From dceff27374ba8d8d036e1ec171df2fa7e2e93a96 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Fri, 21 Jan 2022 15:54:54 -0500
Subject: [PATCH] Squashed PRs 23137, 23141, 23157, 23164

---
 .../src/__tests__/TimelineProfiler-test.js    | 1914 +++++-----
 .../__snapshots__/profilingCache-test.js.snap |   14 +-
 .../src/__tests__/preprocessData-test.js      | 3401 +++++++++--------
 .../src/__tests__/store-test.js               |    8 +-
 .../src/backend/legacy/renderer.js            |    2 +-
 .../src/backend/profilingHooks.js             |  106 +-
 .../src/backend/renderer.js                   |   43 +-
 .../src/backend/types.js                      |    1 +
 .../react-devtools-shared/src/constants.js    |    3 +
 .../src/devtools/store.js                     |   62 +-
 .../Profiler/ClearProfilingDataButton.js      |   21 +-
 .../views/Profiler/CommitTreeBuilder.js       |    2 +-
 .../views/Profiler/NoProfilingData.js         |   35 +
 .../src/devtools/views/Profiler/Profiler.css  |   12 +
 .../src/devtools/views/Profiler/Profiler.js   |   37 +-
 .../views/Profiler/ProfilerContext.js         |    6 +-
 .../views/Profiler/ProfilingNotSupported.js   |   35 +
 .../src/devtools/views/Profiler/types.js      |    2 +
 .../react-devtools-timeline/src/Timeline.js   |   53 +-
 .../src/TimelineContext.js                    |   28 +-
 .../src/TimelineNotSupported.css              |   38 +
 .../src/TimelineNotSupported.js               |   52 +
 22 files changed, 3064 insertions(+), 2811 deletions(-)
 create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
 create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js
 create mode 100644 packages/react-devtools-timeline/src/TimelineNotSupported.css
 create mode 100644 packages/react-devtools-timeline/src/TimelineNotSupported.js

diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
index 1a369acca27bd..2b1908d3c29d0 100644
--- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
@@ -18,1005 +18,766 @@ describe('Timeline profiler', () => {
   let unmountFns;
   let utils;
 
-  let clearedMarks;
-  let featureDetectionMarkName = null;
-  let marks;
-  let setPerformanceMock;
-
-  function createUserTimingPolyfill() {
-    featureDetectionMarkName = null;
-
-    clearedMarks = [];
-    marks = [];
-
-    // Remove file-system specific bits or version-specific bits of information from the module range marks.
-    function filterMarkData(markName) {
-      if (markName.startsWith('--react-internal-module-start')) {
-        return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
-      } else if (markName.startsWith('--react-internal-module-stop')) {
-        return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
-      } else if (markName.startsWith('--react-version')) {
-        return `${markName.substr(0, 15)}-<filtered-version>`;
-      } else {
-        return markName;
+  describe('User Timing API', () => {
+    let clearedMarks;
+    let featureDetectionMarkName = null;
+    let marks;
+    let setPerformanceMock;
+
+    function createUserTimingPolyfill() {
+      featureDetectionMarkName = null;
+
+      clearedMarks = [];
+      marks = [];
+
+      // Remove file-system specific bits or version-specific bits of information from the module range marks.
+      function filterMarkData(markName) {
+        if (markName.startsWith('--react-internal-module-start')) {
+          return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
+        } else if (markName.startsWith('--react-internal-module-stop')) {
+          return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
+        } else if (markName.startsWith('--react-version')) {
+          return `${markName.substr(0, 15)}-<filtered-version>`;
+        } else {
+          return markName;
+        }
       }
+
+      // This is not a true polyfill, but it gives us enough to capture marks.
+      // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
+      return {
+        clearMarks(markName) {
+          markName = filterMarkData(markName);
+
+          clearedMarks.push(markName);
+          marks = marks.filter(mark => mark !== markName);
+        },
+        mark(markName, markOptions) {
+          markName = filterMarkData(markName);
+
+          if (featureDetectionMarkName === null) {
+            featureDetectionMarkName = markName;
+          }
+
+          marks.push(markName);
+
+          if (markOptions != null) {
+            // This is triggers the feature detection.
+            markOptions.startTime++;
+          }
+        },
+      };
     }
 
-    // This is not a true polyfill, but it gives us enough to capture marks.
-    // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
-    return {
-      clearMarks(markName) {
-        markName = filterMarkData(markName);
+    function clearPendingMarks() {
+      clearedMarks.splice(0);
+    }
 
-        clearedMarks.push(markName);
-        marks = marks.filter(mark => mark !== markName);
-      },
-      mark(markName, markOptions) {
-        markName = filterMarkData(markName);
+    function dispatchAndSetCurrentEvent(element, event) {
+      try {
+        window.event = event;
+        element.dispatchEvent(event);
+      } finally {
+        window.event = undefined;
+      }
+    }
 
-        if (featureDetectionMarkName === null) {
-          featureDetectionMarkName = markName;
-        }
+    beforeEach(() => {
+      utils = require('./utils');
+      utils.beforeEachProfiling();
+
+      unmountFns = [];
+      renderHelper = element => {
+        const unmountFn = utils.legacyRender(element);
+        unmountFns.push(unmountFn);
+        return unmountFn;
+      };
+      renderRootHelper = element => {
+        const container = document.createElement('div');
+        const root = ReactDOM.createRoot(container);
+        root.render(element);
+        const unmountFn = () => root.unmount();
+        unmountFns.push(unmountFn);
+        return unmountFn;
+      };
+
+      React = require('react');
+      ReactDOM = require('react-dom');
+      Scheduler = require('scheduler');
+
+      setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
+        .setPerformanceMock_ONLY_FOR_TESTING;
+      setPerformanceMock(createUserTimingPolyfill());
+    });
 
-        marks.push(markName);
+    afterEach(() => {
+      // Verify all logged marks also get cleared.
+      expect(marks).toHaveLength(0);
 
-        if (markOptions != null) {
-          // This is triggers the feature detection.
-          markOptions.startTime++;
-        }
-      },
-    };
-  }
-
-  function clearPendingMarks() {
-    clearedMarks.splice(0);
-  }
-
-  function dispatchAndSetCurrentEvent(element, event) {
-    try {
-      window.event = event;
-      element.dispatchEvent(event);
-    } finally {
-      window.event = undefined;
-    }
-  }
-
-  beforeEach(() => {
-    utils = require('./utils');
-    utils.beforeEachProfiling();
-
-    unmountFns = [];
-    renderHelper = element => {
-      const unmountFn = utils.legacyRender(element);
-      unmountFns.push(unmountFn);
-      return unmountFn;
-    };
-    renderRootHelper = element => {
-      const container = document.createElement('div');
-      const root = ReactDOM.createRoot(container);
-      root.render(element);
-      const unmountFn = () => root.unmount();
-      unmountFns.push(unmountFn);
-      return unmountFn;
-    };
-
-    React = require('react');
-    ReactDOM = require('react-dom');
-    Scheduler = require('scheduler');
-
-    setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
-      .setPerformanceMock_ONLY_FOR_TESTING;
-    setPerformanceMock(createUserTimingPolyfill());
-  });
+      unmountFns.forEach(unmountFn => unmountFn());
 
-  afterEach(() => {
-    // Verify all logged marks also get cleared.
-    expect(marks).toHaveLength(0);
+      setPerformanceMock(null);
+    });
 
-    unmountFns.forEach(unmountFn => unmountFn());
+    describe('when profiling', () => {
+      beforeEach(() => {
+        const store = global.store;
+        utils.act(() => store.profilerStore.startProfiling());
 
-    setPerformanceMock(null);
-  });
+        // Clear inital metadata marks to make tests below less noisy.
+        clearPendingMarks();
+      });
 
-  it('should mark sync render without suspends or state updates', () => {
-    renderHelper(<div />);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-1",
-        "--render-start-1",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-1",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+      it('should mark sync render without suspends or state updates', () => {
+        renderHelper(<div />);
 
-  it('should mark concurrent render without suspends or state updates', () => {
-    renderRootHelper(<div />);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-1",
+          "--render-start-1",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-1",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-      ]
-    `);
+      it('should mark concurrent render without suspends or state updates', () => {
+        renderRootHelper(<div />);
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--schedule-render-16",
+              ]
+          `);
 
-  it('should mark render yields', async () => {
-    function Bar() {
-      Scheduler.unstable_yieldValue('Bar');
-      return null;
-    }
+        clearPendingMarks();
 
-    function Foo() {
-      Scheduler.unstable_yieldValue('Foo');
-      return <Bar />;
-    }
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-    React.startTransition(() => {
-      renderRootHelper(<Foo />);
-    });
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
 
-    // Do one step of work.
-    expect(Scheduler).toFlushAndYieldThrough(['Foo']);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-64",
-        "--render-start-64",
-        "--component-render-start-Foo",
-        "--component-render-stop",
-        "--render-yield",
-      ]
-    `);
-  });
+      it('should mark render yields', async () => {
+        function Bar() {
+          Scheduler.unstable_yieldValue('Bar');
+          return null;
+        }
 
-  it('should mark sync render with suspense that resolves', async () => {
-    const fakeSuspensePromise = Promise.resolve(true);
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+        function Foo() {
+          Scheduler.unstable_yieldValue('Foo');
+          return <Bar />;
+        }
 
-    renderHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-1",
-        "--render-start-1",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--suspense-suspend-0-Example-mount-1-",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-1",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
+        React.startTransition(() => {
+          renderRootHelper(<Foo />);
+        });
+
+        // Do one step of work.
+        expect(Scheduler).toFlushAndYieldThrough(['Foo']);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--schedule-render-64",
+                "--render-start-64",
+                "--component-render-start-Foo",
+                "--component-render-stop",
+                "--render-yield",
+              ]
+          `);
+      });
+
+      it('should mark sync render with suspense that resolves', async () => {
+        const fakeSuspensePromise = Promise.resolve(true);
+        function Example() {
+          throw fakeSuspensePromise;
+        }
 
-    clearPendingMarks();
+        renderHelper(
+          <React.Suspense fallback={null}>
+            <Example />
+          </React.Suspense>,
+        );
 
-    await fakeSuspensePromise;
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
-          "--suspense-resolved-0-Example",
+          "--schedule-render-1",
+          "--render-start-1",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--suspense-suspend-0-Example-mount-1-",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-1",
+          "--layout-effects-stop",
+          "--commit-stop",
         ]
-    `);
-  });
+      `);
 
-  it('should mark sync render with suspense that rejects', async () => {
-    const fakeSuspensePromise = Promise.reject(new Error('error'));
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+        clearPendingMarks();
 
-    renderHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-1",
-        "--render-start-1",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--suspense-suspend-0-Example-mount-1-",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-1",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
+        await fakeSuspensePromise;
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--suspense-resolved-0-Example",
+        ]
     `);
+      });
 
-    clearPendingMarks();
+      it('should mark sync render with suspense that rejects', async () => {
+        const fakeSuspensePromise = Promise.reject(new Error('error'));
+        function Example() {
+          throw fakeSuspensePromise;
+        }
 
-    await expect(fakeSuspensePromise).rejects.toThrow();
-    expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
-  });
+        renderHelper(
+          <React.Suspense fallback={null}>
+            <Example />
+          </React.Suspense>,
+        );
 
-  it('should mark concurrent render with suspense that resolves', async () => {
-    const fakeSuspensePromise = Promise.resolve(true);
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-1",
+          "--render-start-1",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--suspense-suspend-0-Example-mount-1-",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-1",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
 
-    renderRootHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+        clearPendingMarks();
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-      ]
-    `);
+        await expect(fakeSuspensePromise).rejects.toThrow();
+        expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
+      });
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--suspense-suspend-0-Example-mount-16-",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
+      it('should mark concurrent render with suspense that resolves', async () => {
+        const fakeSuspensePromise = Promise.resolve(true);
+        function Example() {
+          throw fakeSuspensePromise;
+        }
 
-    clearPendingMarks();
+        renderRootHelper(
+          <React.Suspense fallback={null}>
+            <Example />
+          </React.Suspense>,
+        );
 
-    await fakeSuspensePromise;
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--suspense-resolved-0-Example",
-      ]
-    `);
-  });
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--schedule-render-16",
+              ]
+          `);
 
-  it('should mark concurrent render with suspense that rejects', async () => {
-    const fakeSuspensePromise = Promise.reject(new Error('error'));
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+        clearPendingMarks();
 
-    renderRootHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-      ]
-    `);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--suspense-suspend-0-Example-mount-16-",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--suspense-suspend-0-Example-mount-16-",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
+        clearPendingMarks();
 
-    clearPendingMarks();
+        await fakeSuspensePromise;
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--suspense-resolved-0-Example",
+              ]
+          `);
+      });
 
-    await expect(fakeSuspensePromise).rejects.toThrow();
-    expect(clearedMarks).toMatchInlineSnapshot(`
-        Array [
-          "--suspense-rejected-0-Example",
-        ]
-    `);
-  });
+      it('should mark concurrent render with suspense that rejects', async () => {
+        const fakeSuspensePromise = Promise.reject(new Error('error'));
+        function Example() {
+          throw fakeSuspensePromise;
+        }
 
-  it('should mark cascading class component state updates', () => {
-    class Example extends React.Component {
-      state = {didMount: false};
-      componentDidMount() {
-        this.setState({didMount: true});
-      }
-      render() {
-        return null;
-      }
-    }
+        renderRootHelper(
+          <React.Suspense fallback={null}>
+            <Example />
+          </React.Suspense>,
+        );
 
-    renderRootHelper(<Example />);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--schedule-render-16",
+              ]
+          `);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-      ]
-    `);
+        clearPendingMarks();
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--schedule-state-update-1-Example",
-        "--layout-effects-stop",
-        "--render-start-1",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-  it('should mark cascading class component force updates', () => {
-    class Example extends React.Component {
-      componentDidMount() {
-        this.forceUpdate();
-      }
-      render() {
-        return null;
-      }
-    }
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--suspense-suspend-0-Example-mount-16-",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
 
-    renderRootHelper(<Example />);
+        clearPendingMarks();
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        await expect(fakeSuspensePromise).rejects.toThrow();
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
-          "--schedule-render-16",
+          "--suspense-rejected-0-Example",
         ]
     `);
+      });
+
+      it('should mark cascading class component state updates', () => {
+        class Example extends React.Component {
+          state = {didMount: false};
+          componentDidMount() {
+            this.setState({didMount: true});
+          }
+          render() {
+            return null;
+          }
+        }
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--schedule-forced-update-1-Example",
-        "--layout-effects-stop",
-        "--render-start-1",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        renderRootHelper(<Example />);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--schedule-render-16",
+              ]
+          `);
+
+        clearPendingMarks();
+
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-  it('should mark render phase state updates for class component', () => {
-    class Example extends React.Component {
-      state = {didRender: false};
-      render() {
-        if (this.state.didRender === false) {
-          this.setState({didRender: true});
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--schedule-state-update-1-Example",
+          "--layout-effects-stop",
+          "--render-start-1",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--commit-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      it('should mark cascading class component force updates', () => {
+        class Example extends React.Component {
+          componentDidMount() {
+            this.forceUpdate();
+          }
+          render() {
+            return null;
+          }
         }
-        return null;
-      }
-    }
 
-    renderRootHelper(<Example />);
+        renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+        clearPendingMarks();
 
-    let errorMessage;
-    spyOn(console, 'error').and.callFake(message => {
-      errorMessage = message;
-    });
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(console.error).toHaveBeenCalledTimes(1);
-    expect(errorMessage).toContain(
-      'Cannot update during an existing state transition',
-    );
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--schedule-state-update-16-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
-
-  it('should mark render phase force updates for class component', () => {
-    class Example extends React.Component {
-      state = {didRender: false};
-      render() {
-        if (this.state.didRender === false) {
-          this.forceUpdate(() => this.setState({didRender: true}));
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--schedule-forced-update-1-Example",
+          "--layout-effects-stop",
+          "--render-start-1",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--commit-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      it('should mark render phase state updates for class component', () => {
+        class Example extends React.Component {
+          state = {didRender: false};
+          render() {
+            if (this.state.didRender === false) {
+              this.setState({didRender: true});
+            }
+            return null;
+          }
         }
-        return null;
-      }
-    }
 
-    renderRootHelper(<Example />);
+        renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+        clearPendingMarks();
 
-    let errorMessage;
-    spyOn(console, 'error').and.callFake(message => {
-      errorMessage = message;
-    });
+        let errorMessage;
+        spyOn(console, 'error').and.callFake(message => {
+          errorMessage = message;
+        });
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(console.error).toHaveBeenCalledTimes(1);
-    expect(errorMessage).toContain(
-      'Cannot update during an existing state transition',
-    );
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--schedule-forced-update-16-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-  it('should mark cascading layout updates', () => {
-    function Example() {
-      const [didMount, setDidMount] = React.useState(false);
-      React.useLayoutEffect(() => {
-        setDidMount(true);
-      }, []);
-      return didMount;
-    }
+        expect(console.error).toHaveBeenCalledTimes(1);
+        expect(errorMessage).toContain(
+          'Cannot update during an existing state transition',
+        );
 
-    renderRootHelper(<Example />);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--schedule-state-update-16-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      it('should mark render phase force updates for class component', () => {
+        class Example extends React.Component {
+          state = {didRender: false};
+          render() {
+            if (this.state.didRender === false) {
+              this.forceUpdate(() => this.setState({didRender: true}));
+            }
+            return null;
+          }
+        }
+
+        renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--component-layout-effect-mount-start-Example",
-        "--schedule-state-update-1-Example",
-        "--component-layout-effect-mount-stop",
-        "--layout-effects-stop",
-        "--render-start-1",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        clearPendingMarks();
 
-  // This test is coupled to lane implementation details, so I'm disabling it in
-  // the new fork until it stabilizes so we don't have to repeatedly update it.
-  it('should mark cascading passive updates', () => {
-    function Example() {
-      const [didMount, setDidMount] = React.useState(false);
-      React.useEffect(() => {
-        setDidMount(true);
-      }, []);
-      return didMount;
-    }
+        let errorMessage;
+        spyOn(console, 'error').and.callFake(message => {
+          errorMessage = message;
+        });
 
-    renderRootHelper(<Example />);
-
-    expect(Scheduler).toFlushAndYield([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-        "--passive-effects-start-16",
-        "--component-passive-effect-mount-start-Example",
-        "--schedule-state-update-16-Example",
-        "--component-passive-effect-mount-stop",
-        "--passive-effects-stop",
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-      ]
-    `);
-  });
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-  it('should mark render phase updates', () => {
-    function Example() {
-      const [didRender, setDidRender] = React.useState(false);
-      if (!didRender) {
-        setDidRender(true);
-      }
-      return didRender;
-    }
+        expect(console.error).toHaveBeenCalledTimes(1);
+        expect(errorMessage).toContain(
+          'Cannot update during an existing state transition',
+        );
 
-    renderRootHelper(<Example />);
-
-    expect(Scheduler).toFlushAndYield([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-        "--render-start-16",
-        "--component-render-start-Example",
-        "--schedule-state-update-16-Example",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
-
-  it('should mark sync render that throws', async () => {
-    spyOn(console, 'error');
-
-    class ErrorBoundary extends React.Component {
-      state = {error: null};
-      componentDidCatch(error) {
-        this.setState({error});
-      }
-      render() {
-        if (this.state.error) {
-          return null;
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--schedule-forced-update-16-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      it('should mark cascading layout updates', () => {
+        function Example() {
+          const [didMount, setDidMount] = React.useState(false);
+          React.useLayoutEffect(() => {
+            setDidMount(true);
+          }, []);
+          return didMount;
         }
-        return this.props.children;
-      }
-    }
 
-    function ExampleThatThrows() {
-      throw Error('Expected error');
-    }
+        renderRootHelper(<Example />);
 
-    renderHelper(
-      <ErrorBoundary>
-        <ExampleThatThrows />
-      </ErrorBoundary>,
-    );
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-1",
-        "--render-start-1",
-        "--component-render-start-ErrorBoundary",
-        "--component-render-stop",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-stop",
-        "--error-ExampleThatThrows-mount-Expected error",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-1",
-        "--schedule-state-update-1-ErrorBoundary",
-        "--layout-effects-stop",
-        "--commit-stop",
-        "--render-start-1",
-        "--component-render-start-ErrorBoundary",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-      ]
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-16",
+        ]
     `);
-  });
 
-  it('should mark concurrent render that throws', async () => {
-    spyOn(console, 'error');
+        clearPendingMarks();
 
-    class ErrorBoundary extends React.Component {
-      state = {error: null};
-      componentDidCatch(error) {
-        this.setState({error});
-      }
-      render() {
-        if (this.state.error) {
-          return null;
+        expect(Scheduler).toFlushUntilNextPaint([]);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--component-layout-effect-mount-start-Example",
+          "--schedule-state-update-1-Example",
+          "--component-layout-effect-mount-stop",
+          "--layout-effects-stop",
+          "--render-start-1",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--commit-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      // This test is coupled to lane implementation details, so I'm disabling it in
+      // the new fork until it stabilizes so we don't have to repeatedly update it.
+      it('should mark cascading passive updates', () => {
+        function Example() {
+          const [didMount, setDidMount] = React.useState(false);
+          React.useEffect(() => {
+            setDidMount(true);
+          }, []);
+          return didMount;
         }
-        return this.props.children;
-      }
-    }
 
-    function ExampleThatThrows() {
-      // eslint-disable-next-line no-throw-literal
-      throw 'Expected error';
-    }
+        renderRootHelper(<Example />);
 
-    renderRootHelper(
-      <ErrorBoundary>
-        <ExampleThatThrows />
-      </ErrorBoundary>,
-    );
+        expect(Scheduler).toFlushAndYield([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+          "--passive-effects-start-16",
+          "--component-passive-effect-mount-start-Example",
+          "--schedule-state-update-16-Example",
+          "--component-passive-effect-mount-stop",
+          "--passive-effects-stop",
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--commit-stop",
         ]
-    `);
-
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushUntilNextPaint([]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--render-start-16",
-        "--component-render-start-ErrorBoundary",
-        "--component-render-stop",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-stop",
-        "--error-ExampleThatThrows-mount-Expected error",
-        "--render-stop",
-        "--render-start-16",
-        "--component-render-start-ErrorBoundary",
-        "--component-render-stop",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-start-ExampleThatThrows",
-        "--component-render-stop",
-        "--error-ExampleThatThrows-mount-Expected error",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--schedule-state-update-1-ErrorBoundary",
-        "--layout-effects-stop",
-        "--render-start-1",
-        "--component-render-start-ErrorBoundary",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--commit-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+      `);
+      });
+
+      it('should mark render phase updates', () => {
+        function Example() {
+          const [didRender, setDidRender] = React.useState(false);
+          if (!didRender) {
+            setDidRender(true);
+          }
+          return didRender;
+        }
 
-  it('should mark passive and layout effects', async () => {
-    function ComponentWithEffects() {
-      React.useLayoutEffect(() => {
-        Scheduler.unstable_yieldValue('layout 1 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('layout 1 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 1 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 1 unmount');
-        };
-      }, []);
-
-      React.useLayoutEffect(() => {
-        Scheduler.unstable_yieldValue('layout 2 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('layout 2 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 2 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 2 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 3 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 3 unmount');
-        };
-      }, []);
-
-      return null;
-    }
+        renderRootHelper(<Example />);
 
-    const unmount = renderRootHelper(<ComponentWithEffects />);
-
-    expect(Scheduler).toFlushUntilNextPaint([
-      'layout 1 mount',
-      'layout 2 mount',
-    ]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-16",
-        "--render-start-16",
-        "--component-render-start-ComponentWithEffects",
-        "--component-render-stop",
-        "--render-stop",
-        "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--layout-effects-start-16",
-        "--component-layout-effect-mount-start-ComponentWithEffects",
-        "--component-layout-effect-mount-stop",
-        "--component-layout-effect-mount-start-ComponentWithEffects",
-        "--component-layout-effect-mount-stop",
-        "--layout-effects-stop",
-        "--commit-stop",
-      ]
-    `);
+        expect(Scheduler).toFlushAndYield([]);
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushAndYield([
-      'passive 1 mount',
-      'passive 2 mount',
-      'passive 3 mount',
-    ]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--passive-effects-start-16",
-        "--component-passive-effect-mount-start-ComponentWithEffects",
-        "--component-passive-effect-mount-stop",
-        "--component-passive-effect-mount-start-ComponentWithEffects",
-        "--component-passive-effect-mount-stop",
-        "--component-passive-effect-mount-start-ComponentWithEffects",
-        "--component-passive-effect-mount-stop",
-        "--passive-effects-stop",
-      ]
-    `);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-16",
+          "--render-start-16",
+          "--component-render-start-Example",
+          "--schedule-state-update-16-Example",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+      });
+
+      it('should mark sync render that throws', async () => {
+        spyOn(console, 'error');
+
+        class ErrorBoundary extends React.Component {
+          state = {error: null};
+          componentDidCatch(error) {
+            this.setState({error});
+          }
+          render() {
+            if (this.state.error) {
+              return null;
+            }
+            return this.props.children;
+          }
+        }
 
-    clearPendingMarks();
-
-    expect(Scheduler).toFlushAndYield([]);
-
-    unmount();
-
-    expect(Scheduler).toHaveYielded([
-      'layout 1 unmount',
-      'layout 2 unmount',
-      'passive 1 unmount',
-      'passive 2 unmount',
-      'passive 3 unmount',
-    ]);
-
-    expect(clearedMarks).toMatchInlineSnapshot(`
-      Array [
-        "--schedule-render-1",
-        "--render-start-1",
-        "--render-stop",
-        "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-        "--component-layout-effect-unmount-start-ComponentWithEffects",
-        "--component-layout-effect-unmount-stop",
-        "--component-layout-effect-unmount-start-ComponentWithEffects",
-        "--component-layout-effect-unmount-stop",
-        "--layout-effects-start-1",
-        "--layout-effects-stop",
-        "--passive-effects-start-1",
-        "--component-passive-effect-unmount-start-ComponentWithEffects",
-        "--component-passive-effect-unmount-stop",
-        "--component-passive-effect-unmount-start-ComponentWithEffects",
-        "--component-passive-effect-unmount-stop",
-        "--component-passive-effect-unmount-start-ComponentWithEffects",
-        "--component-passive-effect-unmount-stop",
-        "--passive-effects-stop",
-        "--commit-stop",
-      ]
-    `);
-  });
+        function ExampleThatThrows() {
+          throw Error('Expected error');
+        }
 
-  describe('lane labels', () => {
-    it('regression test SyncLane', () => {
-      renderHelper(<div />);
+        renderHelper(
+          <ErrorBoundary>
+            <ExampleThatThrows />
+          </ErrorBoundary>,
+        );
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-1",
           "--render-start-1",
+          "--component-render-start-ErrorBoundary",
+          "--component-render-stop",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-stop",
+          "--error-ExampleThatThrows-mount-Expected error",
           "--render-stop",
           "--commit-start-1",
           "--react-version-<filtered-version>",
@@ -1025,47 +786,90 @@ describe('Timeline profiler', () => {
           "--react-internal-module-stop-<filtered-file-system-path>",
           "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
           "--layout-effects-start-1",
+          "--schedule-state-update-1-ErrorBoundary",
           "--layout-effects-stop",
           "--commit-stop",
+          "--render-start-1",
+          "--component-render-start-ErrorBoundary",
+          "--component-render-stop",
+          "--render-stop",
+          "--commit-start-1",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--commit-stop",
         ]
       `);
-    });
-
-    it('regression test DefaultLane', () => {
-      renderRootHelper(<div />);
-      expect(clearedMarks).toMatchInlineSnapshot(`
-          Array [
-            "--schedule-render-16",
-          ]
-      `);
-    });
-
-    it('regression test InputDiscreteLane', async () => {
-      const targetRef = React.createRef(null);
+      });
+
+      it('should mark concurrent render that throws', async () => {
+        spyOn(console, 'error');
+
+        class ErrorBoundary extends React.Component {
+          state = {error: null};
+          componentDidCatch(error) {
+            this.setState({error});
+          }
+          render() {
+            if (this.state.error) {
+              return null;
+            }
+            return this.props.children;
+          }
+        }
 
-      function App() {
-        const [count, setCount] = React.useState(0);
-        const handleClick = () => {
-          setCount(count + 1);
-        };
-        return <button ref={targetRef} onClick={handleClick} />;
-      }
+        function ExampleThatThrows() {
+          // eslint-disable-next-line no-throw-literal
+          throw 'Expected error';
+        }
 
-      renderRootHelper(<App />);
-      expect(Scheduler).toFlushAndYield([]);
+        renderRootHelper(
+          <ErrorBoundary>
+            <ExampleThatThrows />
+          </ErrorBoundary>,
+        );
 
-      clearedMarks.splice(0);
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-16",
+        ]
+    `);
 
-      targetRef.current.click();
+        clearPendingMarks();
 
-      // Wait a frame, for React to process the "click" update.
-      await Promise.resolve();
+        expect(Scheduler).toFlushUntilNextPaint([]);
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
-          "--schedule-state-update-1-App",
+          "--render-start-16",
+          "--component-render-start-ErrorBoundary",
+          "--component-render-stop",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-stop",
+          "--error-ExampleThatThrows-mount-Expected error",
+          "--render-stop",
+          "--render-start-16",
+          "--component-render-start-ErrorBoundary",
+          "--component-render-stop",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-start-ExampleThatThrows",
+          "--component-render-stop",
+          "--error-ExampleThatThrows-mount-Expected error",
+          "--render-stop",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--schedule-state-update-1-ErrorBoundary",
+          "--layout-effects-stop",
           "--render-start-1",
-          "--component-render-start-App",
+          "--component-render-start-ErrorBoundary",
           "--component-render-stop",
           "--render-stop",
           "--commit-start-1",
@@ -1074,51 +878,267 @@ describe('Timeline profiler', () => {
           "--react-internal-module-start-<filtered-file-system-path>",
           "--react-internal-module-stop-<filtered-file-system-path>",
           "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-          "--layout-effects-start-1",
-          "--layout-effects-stop",
+          "--commit-stop",
           "--commit-stop",
         ]
       `);
-    });
-
-    it('regression test InputContinuousLane', async () => {
-      const targetRef = React.createRef(null);
+      });
+
+      it('should mark passive and layout effects', async () => {
+        function ComponentWithEffects() {
+          React.useLayoutEffect(() => {
+            Scheduler.unstable_yieldValue('layout 1 mount');
+            return () => {
+              Scheduler.unstable_yieldValue('layout 1 unmount');
+            };
+          }, []);
+
+          React.useEffect(() => {
+            Scheduler.unstable_yieldValue('passive 1 mount');
+            return () => {
+              Scheduler.unstable_yieldValue('passive 1 unmount');
+            };
+          }, []);
+
+          React.useLayoutEffect(() => {
+            Scheduler.unstable_yieldValue('layout 2 mount');
+            return () => {
+              Scheduler.unstable_yieldValue('layout 2 unmount');
+            };
+          }, []);
+
+          React.useEffect(() => {
+            Scheduler.unstable_yieldValue('passive 2 mount');
+            return () => {
+              Scheduler.unstable_yieldValue('passive 2 unmount');
+            };
+          }, []);
+
+          React.useEffect(() => {
+            Scheduler.unstable_yieldValue('passive 3 mount');
+            return () => {
+              Scheduler.unstable_yieldValue('passive 3 unmount');
+            };
+          }, []);
 
-      function App() {
-        const [count, setCount] = React.useState(0);
-        const handleMouseOver = () => setCount(count + 1);
-        return <div ref={targetRef} onMouseOver={handleMouseOver} />;
-      }
-
-      renderRootHelper(<App />);
-      expect(Scheduler).toFlushAndYield([]);
-
-      clearedMarks.splice(0);
+          return null;
+        }
 
-      const event = document.createEvent('MouseEvents');
-      event.initEvent('mouseover', true, true);
-      dispatchAndSetCurrentEvent(targetRef.current, event);
+        const unmount = renderRootHelper(<ComponentWithEffects />);
 
-      expect(Scheduler).toFlushAndYield([]);
+        expect(Scheduler).toFlushUntilNextPaint([
+          'layout 1 mount',
+          'layout 2 mount',
+        ]);
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
-          "--schedule-state-update-4-App",
-          "--render-start-4",
-          "--component-render-start-App",
+          "--schedule-render-16",
+          "--render-start-16",
+          "--component-render-start-ComponentWithEffects",
           "--component-render-stop",
           "--render-stop",
-          "--commit-start-4",
+          "--commit-start-16",
+          "--react-version-<filtered-version>",
+          "--profiler-version-1",
+          "--react-internal-module-start-<filtered-file-system-path>",
+          "--react-internal-module-stop-<filtered-file-system-path>",
+          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+          "--layout-effects-start-16",
+          "--component-layout-effect-mount-start-ComponentWithEffects",
+          "--component-layout-effect-mount-stop",
+          "--component-layout-effect-mount-start-ComponentWithEffects",
+          "--component-layout-effect-mount-stop",
+          "--layout-effects-stop",
+          "--commit-stop",
+        ]
+      `);
+
+        clearPendingMarks();
+
+        expect(Scheduler).toFlushAndYield([
+          'passive 1 mount',
+          'passive 2 mount',
+          'passive 3 mount',
+        ]);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`
+              Array [
+                "--passive-effects-start-16",
+                "--component-passive-effect-mount-start-ComponentWithEffects",
+                "--component-passive-effect-mount-stop",
+                "--component-passive-effect-mount-start-ComponentWithEffects",
+                "--component-passive-effect-mount-stop",
+                "--component-passive-effect-mount-start-ComponentWithEffects",
+                "--component-passive-effect-mount-stop",
+                "--passive-effects-stop",
+              ]
+          `);
+
+        clearPendingMarks();
+
+        expect(Scheduler).toFlushAndYield([]);
+
+        unmount();
+
+        expect(Scheduler).toHaveYielded([
+          'layout 1 unmount',
+          'layout 2 unmount',
+          'passive 1 unmount',
+          'passive 2 unmount',
+          'passive 3 unmount',
+        ]);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`
+        Array [
+          "--schedule-render-1",
+          "--render-start-1",
+          "--render-stop",
+          "--commit-start-1",
           "--react-version-<filtered-version>",
           "--profiler-version-1",
           "--react-internal-module-start-<filtered-file-system-path>",
           "--react-internal-module-stop-<filtered-file-system-path>",
           "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
-          "--layout-effects-start-4",
+          "--component-layout-effect-unmount-start-ComponentWithEffects",
+          "--component-layout-effect-unmount-stop",
+          "--component-layout-effect-unmount-start-ComponentWithEffects",
+          "--component-layout-effect-unmount-stop",
+          "--layout-effects-start-1",
           "--layout-effects-stop",
+          "--passive-effects-start-1",
+          "--component-passive-effect-unmount-start-ComponentWithEffects",
+          "--component-passive-effect-unmount-stop",
+          "--component-passive-effect-unmount-start-ComponentWithEffects",
+          "--component-passive-effect-unmount-stop",
+          "--component-passive-effect-unmount-start-ComponentWithEffects",
+          "--component-passive-effect-unmount-stop",
+          "--passive-effects-stop",
           "--commit-stop",
         ]
       `);
+      });
+
+      describe('lane labels', () => {
+        it('regression test SyncLane', () => {
+          renderHelper(<div />);
+
+          expect(clearedMarks).toMatchInlineSnapshot(`
+          Array [
+            "--schedule-render-1",
+            "--render-start-1",
+            "--render-stop",
+            "--commit-start-1",
+            "--react-version-<filtered-version>",
+            "--profiler-version-1",
+            "--react-internal-module-start-<filtered-file-system-path>",
+            "--react-internal-module-stop-<filtered-file-system-path>",
+            "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+            "--layout-effects-start-1",
+            "--layout-effects-stop",
+            "--commit-stop",
+          ]
+        `);
+        });
+
+        it('regression test DefaultLane', () => {
+          renderRootHelper(<div />);
+          expect(clearedMarks).toMatchInlineSnapshot(`
+          Array [
+            "--schedule-render-16",
+          ]
+      `);
+        });
+
+        it('regression test InputDiscreteLane', async () => {
+          const targetRef = React.createRef(null);
+
+          function App() {
+            const [count, setCount] = React.useState(0);
+            const handleClick = () => {
+              setCount(count + 1);
+            };
+            return <button ref={targetRef} onClick={handleClick} />;
+          }
+
+          renderRootHelper(<App />);
+          expect(Scheduler).toFlushAndYield([]);
+
+          clearedMarks.splice(0);
+
+          targetRef.current.click();
+
+          // Wait a frame, for React to process the "click" update.
+          await Promise.resolve();
+
+          expect(clearedMarks).toMatchInlineSnapshot(`
+          Array [
+            "--schedule-state-update-1-App",
+            "--render-start-1",
+            "--component-render-start-App",
+            "--component-render-stop",
+            "--render-stop",
+            "--commit-start-1",
+            "--react-version-<filtered-version>",
+            "--profiler-version-1",
+            "--react-internal-module-start-<filtered-file-system-path>",
+            "--react-internal-module-stop-<filtered-file-system-path>",
+            "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+            "--layout-effects-start-1",
+            "--layout-effects-stop",
+            "--commit-stop",
+          ]
+        `);
+        });
+
+        it('regression test InputContinuousLane', async () => {
+          const targetRef = React.createRef(null);
+
+          function App() {
+            const [count, setCount] = React.useState(0);
+            const handleMouseOver = () => setCount(count + 1);
+            return <div ref={targetRef} onMouseOver={handleMouseOver} />;
+          }
+
+          renderRootHelper(<App />);
+          expect(Scheduler).toFlushAndYield([]);
+
+          clearedMarks.splice(0);
+
+          const event = document.createEvent('MouseEvents');
+          event.initEvent('mouseover', true, true);
+          dispatchAndSetCurrentEvent(targetRef.current, event);
+
+          expect(Scheduler).toFlushAndYield([]);
+
+          expect(clearedMarks).toMatchInlineSnapshot(`
+          Array [
+            "--schedule-state-update-4-App",
+            "--render-start-4",
+            "--component-render-start-App",
+            "--component-render-stop",
+            "--render-stop",
+            "--commit-start-4",
+            "--react-version-<filtered-version>",
+            "--profiler-version-1",
+            "--react-internal-module-start-<filtered-file-system-path>",
+            "--react-internal-module-stop-<filtered-file-system-path>",
+            "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
+            "--layout-effects-start-4",
+            "--layout-effects-stop",
+            "--commit-stop",
+          ]
+        `);
+        });
+      });
+    });
+
+    describe('when not profiling', () => {
+      it('should not log any marks', () => {
+        renderHelper(<div />);
+
+        expect(clearedMarks).toMatchInlineSnapshot(`Array []`);
+      });
     });
   });
 });
diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
index dd4717bb5cb6b..450d176f0ac55 100644
--- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
@@ -720,7 +720,7 @@ Object {
           1,
           11,
           0,
-          1,
+          3,
           1,
           1,
           4,
@@ -1186,7 +1186,7 @@ Object {
           1,
           11,
           0,
-          1,
+          3,
           1,
           1,
           4,
@@ -1663,7 +1663,7 @@ Object {
       13,
       11,
       0,
-      1,
+      3,
       1,
       1,
       4,
@@ -2209,7 +2209,7 @@ Object {
           13,
           11,
           0,
-          1,
+          3,
           1,
           1,
           4,
@@ -2304,7 +2304,7 @@ Object {
       1,
       11,
       0,
-      1,
+      3,
       1,
       1,
       1,
@@ -2954,7 +2954,7 @@ Object {
           1,
           11,
           0,
-          1,
+          3,
           1,
           1,
           1,
@@ -4227,7 +4227,7 @@ Object {
           1,
           11,
           0,
-          1,
+          3,
           1,
           1,
           1,
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index 09ad50ecae49e..d244858b70bb7 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -15,1915 +15,1926 @@ describe('Timeline profiler', () => {
   let Scheduler;
   let utils;
 
-  let clearedMarks;
-  let featureDetectionMarkName = null;
-  let marks;
-  let setPerformanceMock;
-
-  function createUserTimingPolyfill() {
-    featureDetectionMarkName = null;
-
-    clearedMarks = [];
-    marks = [];
-
-    // Remove file-system specific bits or version-specific bits of information from the module range marks.
-    function filterMarkData(markName) {
-      if (markName.startsWith('--react-internal-module-start')) {
-        return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
-      } else if (markName.startsWith('--react-internal-module-stop')) {
-        return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
-      } else if (markName.startsWith('--react-version')) {
-        return `${markName.substr(0, 15)}-0.0.0`;
-      } else {
-        return markName;
-      }
-    }
-
-    // This is not a true polyfill, but it gives us enough to capture marks.
-    // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
-    return {
-      clearMarks(markName) {
-        markName = filterMarkData(markName);
-
-        clearedMarks.push(markName);
-        marks = marks.filter(mark => mark !== markName);
-      },
-      mark(markName, markOptions) {
-        markName = filterMarkData(markName);
-
-        if (featureDetectionMarkName === null) {
-          featureDetectionMarkName = markName;
-        }
-
-        marks.push(markName);
-
-        if (markOptions != null) {
-          // This is triggers the feature detection.
-          markOptions.startTime++;
+  describe('User Timing API', () => {
+    let clearedMarks;
+    let featureDetectionMarkName = null;
+    let marks;
+    let setPerformanceMock;
+
+    function createUserTimingPolyfill() {
+      featureDetectionMarkName = null;
+
+      clearedMarks = [];
+      marks = [];
+
+      // Remove file-system specific bits or version-specific bits of information from the module range marks.
+      function filterMarkData(markName) {
+        if (markName.startsWith('--react-internal-module-start')) {
+          return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
+        } else if (markName.startsWith('--react-internal-module-stop')) {
+          return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
+        } else if (markName.startsWith('--react-version')) {
+          return `${markName.substr(0, 15)}-0.0.0`;
+        } else {
+          return markName;
         }
-      },
-    };
-  }
-
-  function clearPendingMarks() {
-    clearedMarks.splice(0);
-  }
-
-  beforeEach(() => {
-    utils = require('./utils');
-    utils.beforeEachProfiling();
+      }
 
-    React = require('react');
-    ReactDOM = require('react-dom');
-    Scheduler = require('scheduler');
+      // This is not a true polyfill, but it gives us enough to capture marks.
+      // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
+      return {
+        clearMarks(markName) {
+          markName = filterMarkData(markName);
 
-    setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
-      .setPerformanceMock_ONLY_FOR_TESTING;
-    setPerformanceMock(createUserTimingPolyfill());
+          clearedMarks.push(markName);
+          marks = marks.filter(mark => mark !== markName);
+        },
+        mark(markName, markOptions) {
+          markName = filterMarkData(markName);
 
-    global.IS_REACT_ACT_ENVIRONMENT = true;
-  });
+          if (featureDetectionMarkName === null) {
+            featureDetectionMarkName = markName;
+          }
 
-  afterEach(() => {
-    // Verify all logged marks also get cleared.
-    expect(marks).toHaveLength(0);
+          marks.push(markName);
 
-    setPerformanceMock(null);
-  });
+          if (markOptions != null) {
+            // This is triggers the feature detection.
+            markOptions.startTime++;
+          }
+        },
+      };
+    }
 
-  describe('getLanesFromTransportDecimalBitmask', () => {
-    let getLanesFromTransportDecimalBitmask;
+    function clearPendingMarks() {
+      clearedMarks.splice(0);
+    }
 
     beforeEach(() => {
-      getLanesFromTransportDecimalBitmask = require('react-devtools-timeline/src/import-worker/preprocessData')
-        .getLanesFromTransportDecimalBitmask;
-    });
+      utils = require('./utils');
+      utils.beforeEachProfiling();
 
-    it('should return array of lane numbers from bitmask string', () => {
-      expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
-      expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
-      expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
-      expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
-        1,
-        4,
-        6,
-        7,
-        10,
-      ]); // 2 + 16 + 64 + 128 + 1024
-      expect(
-        getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
-      ).toEqual([30]);
-      expect(
-        getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
-      ).toEqual(Array.from(Array(31).keys()));
-    });
+      React = require('react');
+      ReactDOM = require('react-dom');
+      Scheduler = require('scheduler');
 
-    it('should return empty array if laneBitmaskString is not a bitmask', () => {
-      expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
-      expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
-      expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
-      expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
-    });
+      setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
+        .setPerformanceMock_ONLY_FOR_TESTING;
+      setPerformanceMock(createUserTimingPolyfill());
 
-    it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
-      const REACT_TOTAL_NUM_LANES = require('react-devtools-timeline/src/constants')
-        .REACT_TOTAL_NUM_LANES;
+      const store = global.store;
 
-      // Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
-      expect(REACT_TOTAL_NUM_LANES).toBe(31);
+      // Start profiling so that data will actually be recorded.
+      utils.act(() => store.profilerStore.startProfiling());
 
-      expect(
-        getLanesFromTransportDecimalBitmask(
-          '4294967297', // 2^32 + 1
-        ),
-      ).toEqual([0]);
+      global.IS_REACT_ACT_ENVIRONMENT = true;
     });
-  });
 
-  describe('preprocessData', () => {
-    let preprocessData;
+    afterEach(() => {
+      // Verify all logged marks also get cleared.
+      expect(marks).toHaveLength(0);
 
-    beforeEach(() => {
-      preprocessData = require('react-devtools-timeline/src/import-worker/preprocessData')
-        .default;
+      setPerformanceMock(null);
     });
 
-    // These should be dynamic to mimic a real profile,
-    // but reprooducible between test runs.
-    let pid = 0;
-    let tid = 0;
-    let startTime = 0;
+    describe('getLanesFromTransportDecimalBitmask', () => {
+      let getLanesFromTransportDecimalBitmask;
 
-    function createUserTimingEntry(data) {
-      return {
-        pid: ++pid,
-        tid: ++tid,
-        ts: ++startTime,
-        ...data,
-      };
-    }
-
-    function createProfilerVersionEntry() {
-      const SCHEDULING_PROFILER_VERSION = require('react-devtools-timeline/src/constants')
-        .SCHEDULING_PROFILER_VERSION;
-      return createUserTimingEntry({
-        cat: 'blink.user_timing',
-        name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
+      beforeEach(() => {
+        getLanesFromTransportDecimalBitmask = require('react-devtools-timeline/src/import-worker/preprocessData')
+          .getLanesFromTransportDecimalBitmask;
       });
-    }
 
-    function createReactVersionEntry() {
-      return createUserTimingEntry({
-        cat: 'blink.user_timing',
-        name: '--react-version-0.0.0',
+      it('should return array of lane numbers from bitmask string', () => {
+        expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
+        expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
+        expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
+        expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
+          1,
+          4,
+          6,
+          7,
+          10,
+        ]); // 2 + 16 + 64 + 128 + 1024
+        expect(
+          getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
+        ).toEqual([30]);
+        expect(
+          getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
+        ).toEqual(Array.from(Array(31).keys()));
       });
-    }
 
-    function createLaneLabelsEntry() {
-      return createUserTimingEntry({
-        cat: 'blink.user_timing',
-        name:
-          '--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen',
+      it('should return empty array if laneBitmaskString is not a bitmask', () => {
+        expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
+        expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
+        expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
+        expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
       });
-    }
 
-    function createNativeEventEntry(type, duration) {
-      return createUserTimingEntry({
-        cat: 'devtools.timeline',
-        name: 'EventDispatch',
-        args: {data: {type}},
-        dur: duration,
-        tdur: duration,
-      });
-    }
+      it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
+        const REACT_TOTAL_NUM_LANES = require('react-devtools-timeline/src/constants')
+          .REACT_TOTAL_NUM_LANES;
 
-    function creactCpuProfilerSample() {
-      return createUserTimingEntry({
-        args: {data: {startTime: ++startTime}},
-        cat: 'disabled-by-default-v8.cpu_profiler',
-        id: '0x1',
-        name: 'Profile',
-        ph: 'P',
-      });
-    }
+        // Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
+        expect(REACT_TOTAL_NUM_LANES).toBe(31);
 
-    function createBoilerplateEntries() {
-      return [
-        createProfilerVersionEntry(),
-        createReactVersionEntry(),
-        createLaneLabelsEntry(),
-      ];
-    }
+        expect(
+          getLanesFromTransportDecimalBitmask(
+            '4294967297', // 2^32 + 1
+          ),
+        ).toEqual([0]);
+      });
+    });
 
-    function createUserTimingData(sampleMarks) {
-      const cpuProfilerSample = creactCpuProfilerSample();
+    describe('preprocessData', () => {
+      let preprocessData;
 
-      const randomSample = createUserTimingEntry({
-        dur: 100,
-        tdur: 200,
-        ph: 'X',
-        cat: 'disabled-by-default-devtools.timeline',
-        name: 'RunTask',
-        args: {},
+      beforeEach(() => {
+        preprocessData = require('react-devtools-timeline/src/import-worker/preprocessData')
+          .default;
       });
 
-      const userTimingData = [cpuProfilerSample, randomSample];
+      // These should be dynamic to mimic a real profile,
+      // but reprooducible between test runs.
+      let pid = 0;
+      let tid = 0;
+      let startTime = 0;
 
-      sampleMarks.forEach(markName => {
-        userTimingData.push({
+      function createUserTimingEntry(data) {
+        return {
           pid: ++pid,
           tid: ++tid,
           ts: ++startTime,
-          args: {data: {}},
+          ...data,
+        };
+      }
+
+      function createProfilerVersionEntry() {
+        const SCHEDULING_PROFILER_VERSION = require('react-devtools-timeline/src/constants')
+          .SCHEDULING_PROFILER_VERSION;
+        return createUserTimingEntry({
           cat: 'blink.user_timing',
-          name: markName,
-          ph: 'R',
+          name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
         });
-      });
+      }
 
-      return userTimingData;
-    }
+      function createReactVersionEntry() {
+        return createUserTimingEntry({
+          cat: 'blink.user_timing',
+          name: '--react-version-0.0.0',
+        });
+      }
 
-    beforeEach(() => {
-      tid = 0;
-      pid = 0;
-      startTime = 0;
-    });
+      function createLaneLabelsEntry() {
+        return createUserTimingEntry({
+          cat: 'blink.user_timing',
+          name:
+            '--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen',
+        });
+      }
 
-    it('should throw given an empty timeline', async () => {
-      await expect(async () => preprocessData([])).rejects.toThrow();
-    });
+      function createNativeEventEntry(type, duration) {
+        return createUserTimingEntry({
+          cat: 'devtools.timeline',
+          name: 'EventDispatch',
+          args: {data: {type}},
+          dur: duration,
+          tdur: duration,
+        });
+      }
 
-    it('should throw given a timeline with no Profile event', async () => {
-      const randomSample = createUserTimingEntry({
-        dur: 100,
-        tdur: 200,
-        ph: 'X',
-        cat: 'disabled-by-default-devtools.timeline',
-        name: 'RunTask',
-        args: {},
-      });
+      function creactCpuProfilerSample() {
+        return createUserTimingEntry({
+          args: {data: {startTime: ++startTime}},
+          cat: 'disabled-by-default-v8.cpu_profiler',
+          id: '0x1',
+          name: 'Profile',
+          ph: 'P',
+        });
+      }
 
-      await expect(async () =>
-        preprocessData([randomSample]),
-      ).rejects.toThrow();
-    });
+      function createBoilerplateEntries() {
+        return [
+          createProfilerVersionEntry(),
+          createReactVersionEntry(),
+          createLaneLabelsEntry(),
+        ];
+      }
 
-    it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
-      const cpuProfilerSample = creactCpuProfilerSample();
+      function createUserTimingData(sampleMarks) {
+        const cpuProfilerSample = creactCpuProfilerSample();
 
-      await expect(
-        async () => await preprocessData([cpuProfilerSample]),
-      ).rejects.toThrow(
-        'Please provide profiling data from an React application',
-      );
-    });
+        const randomSample = createUserTimingEntry({
+          dur: 100,
+          tdur: 200,
+          ph: 'X',
+          cat: 'disabled-by-default-devtools.timeline',
+          name: 'RunTask',
+          args: {},
+        });
+
+        const userTimingData = [cpuProfilerSample, randomSample];
+
+        sampleMarks.forEach(markName => {
+          userTimingData.push({
+            pid: ++pid,
+            tid: ++tid,
+            ts: ++startTime,
+            args: {data: {}},
+            cat: 'blink.user_timing',
+            name: markName,
+            ph: 'R',
+          });
+        });
 
-    it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
-      const cpuProfilerSample = creactCpuProfilerSample();
-      const scheduleRenderSample = createUserTimingEntry({
-        cat: 'blink.user_timing',
-        name: '--schedule-render-512-',
+        return userTimingData;
+      }
+
+      beforeEach(() => {
+        tid = 0;
+        pid = 0;
+        startTime = 0;
       });
-      const samples = [cpuProfilerSample, scheduleRenderSample];
 
-      await expect(async () => await preprocessData(samples)).rejects.toThrow(
-        'This version of profiling data is not supported',
-      );
-    });
+      it('should throw given an empty timeline', async () => {
+        await expect(async () => preprocessData([])).rejects.toThrow();
+      });
+
+      it('should throw given a timeline with no Profile event', async () => {
+        const randomSample = createUserTimingEntry({
+          dur: 100,
+          tdur: 200,
+          ph: 'X',
+          cat: 'disabled-by-default-devtools.timeline',
+          name: 'RunTask',
+          args: {},
+        });
 
-    it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
-      const cpuProfilerSample = creactCpuProfilerSample();
-      const randomSample = createUserTimingEntry({
-        dur: 100,
-        tdur: 200,
-        ph: 'X',
-        cat: 'disabled-by-default-devtools.timeline',
-        name: 'RunTask',
-        args: {},
+        await expect(async () =>
+          preprocessData([randomSample]),
+        ).rejects.toThrow();
       });
 
-      const data = await preprocessData([
-        ...createBoilerplateEntries(),
-        cpuProfilerSample,
-        randomSample,
-      ]);
-      expect(data).toMatchInlineSnapshot(`
-      Object {
-        "batchUIDToMeasuresMap": Map {},
-        "componentMeasures": Array [],
-        "duration": 0.005,
-        "flamechart": Array [],
-        "internalModuleSourceToRanges": Map {},
-        "laneToLabelMap": Map {
-          0 => "Sync",
-          1 => "InputContinuousHydration",
-          2 => "InputContinuous",
-          3 => "DefaultHydration",
-          4 => "Default",
-          5 => "TransitionHydration",
-          6 => "Transition",
-          7 => "Transition",
-          8 => "Transition",
-          9 => "Transition",
-          10 => "Transition",
-          11 => "Transition",
-          12 => "Transition",
-          13 => "Transition",
-          14 => "Transition",
-          15 => "Transition",
-          16 => "Transition",
-          17 => "Transition",
-          18 => "Transition",
-          19 => "Transition",
-          20 => "Transition",
-          21 => "Transition",
-          22 => "Retry",
-          23 => "Retry",
-          24 => "Retry",
-          25 => "Retry",
-          26 => "Retry",
-          27 => "SelectiveHydration",
-          28 => "IdleHydration",
-          29 => "Idle",
-          30 => "Offscreen",
-        },
-        "laneToReactMeasureMap": Map {
-          0 => Array [],
-          1 => Array [],
-          2 => Array [],
-          3 => Array [],
-          4 => Array [],
-          5 => Array [],
-          6 => Array [],
-          7 => Array [],
-          8 => Array [],
-          9 => Array [],
-          10 => Array [],
-          11 => Array [],
-          12 => Array [],
-          13 => Array [],
-          14 => Array [],
-          15 => Array [],
-          16 => Array [],
-          17 => Array [],
-          18 => Array [],
-          19 => Array [],
-          20 => Array [],
-          21 => Array [],
-          22 => Array [],
-          23 => Array [],
-          24 => Array [],
-          25 => Array [],
-          26 => Array [],
-          27 => Array [],
-          28 => Array [],
-          29 => Array [],
-          30 => Array [],
-        },
-        "nativeEvents": Array [],
-        "networkMeasures": Array [],
-        "otherUserTimingMarks": Array [],
-        "reactVersion": "0.0.0",
-        "schedulingEvents": Array [],
-        "snapshotHeight": 0,
-        "snapshots": Array [],
-        "startTime": 1,
-        "suspenseEvents": Array [],
-        "thrownErrors": Array [],
-      }
-    `);
-    });
+      it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
+        const cpuProfilerSample = creactCpuProfilerSample();
 
-    it('should process legacy data format (before lane labels were added)', async () => {
-      const cpuProfilerSample = creactCpuProfilerSample();
+        await expect(
+          async () => await preprocessData([cpuProfilerSample]),
+        ).rejects.toThrow(
+          'Please provide profiling data from an React application',
+        );
+      });
 
-      // Data below is hard-coded based on an older profile sample.
-      // Should be fine since this is explicitly a legacy-format test.
-      const data = await preprocessData([
-        ...createBoilerplateEntries(),
-        cpuProfilerSample,
-        createUserTimingEntry({
+      it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
+        const cpuProfilerSample = creactCpuProfilerSample();
+        const scheduleRenderSample = createUserTimingEntry({
           cat: 'blink.user_timing',
           name: '--schedule-render-512-',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--render-start-512',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--render-stop',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--commit-start-512',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--layout-effects-start-512',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--layout-effects-stop',
-        }),
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--commit-stop',
-        }),
-      ]);
-      expect(data).toMatchInlineSnapshot(`
-      Object {
-        "batchUIDToMeasuresMap": Map {
-          0 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.005,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.006,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.001,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.006,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.008,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.009,
-              "type": "layout-effects",
-            },
-          ],
-        },
-        "componentMeasures": Array [],
-        "duration": 0.011,
-        "flamechart": Array [],
-        "internalModuleSourceToRanges": Map {},
-        "laneToLabelMap": Map {
-          0 => "Sync",
-          1 => "InputContinuousHydration",
-          2 => "InputContinuous",
-          3 => "DefaultHydration",
-          4 => "Default",
-          5 => "TransitionHydration",
-          6 => "Transition",
-          7 => "Transition",
-          8 => "Transition",
-          9 => "Transition",
-          10 => "Transition",
-          11 => "Transition",
-          12 => "Transition",
-          13 => "Transition",
-          14 => "Transition",
-          15 => "Transition",
-          16 => "Transition",
-          17 => "Transition",
-          18 => "Transition",
-          19 => "Transition",
-          20 => "Transition",
-          21 => "Transition",
-          22 => "Retry",
-          23 => "Retry",
-          24 => "Retry",
-          25 => "Retry",
-          26 => "Retry",
-          27 => "SelectiveHydration",
-          28 => "IdleHydration",
-          29 => "Idle",
-          30 => "Offscreen",
-        },
-        "laneToReactMeasureMap": Map {
-          0 => Array [],
-          1 => Array [],
-          2 => Array [],
-          3 => Array [],
-          4 => Array [],
-          5 => Array [],
-          6 => Array [],
-          7 => Array [],
-          8 => Array [],
-          9 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.005,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.006,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.001,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.006,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.008,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                9,
-              ],
-              "timestamp": 0.009,
-              "type": "layout-effects",
-            },
-          ],
-          10 => Array [],
-          11 => Array [],
-          12 => Array [],
-          13 => Array [],
-          14 => Array [],
-          15 => Array [],
-          16 => Array [],
-          17 => Array [],
-          18 => Array [],
-          19 => Array [],
-          20 => Array [],
-          21 => Array [],
-          22 => Array [],
-          23 => Array [],
-          24 => Array [],
-          25 => Array [],
-          26 => Array [],
-          27 => Array [],
-          28 => Array [],
-          29 => Array [],
-          30 => Array [],
-        },
-        "nativeEvents": Array [],
-        "networkMeasures": Array [],
-        "otherUserTimingMarks": Array [],
-        "reactVersion": "0.0.0",
-        "schedulingEvents": Array [
-          Object {
-            "lanes": Array [
-              9,
-            ],
-            "timestamp": 0.005,
-            "type": "schedule-render",
-            "warning": null,
-          },
-        ],
-        "snapshotHeight": 0,
-        "snapshots": Array [],
-        "startTime": 1,
-        "suspenseEvents": Array [],
-        "thrownErrors": Array [],
-      }
-    `);
-    });
+        });
+        const samples = [cpuProfilerSample, scheduleRenderSample];
+
+        await expect(async () => await preprocessData(samples)).rejects.toThrow(
+          'This version of profiling data is not supported',
+        );
+      });
+
+      it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
+        const cpuProfilerSample = creactCpuProfilerSample();
+        const randomSample = createUserTimingEntry({
+          dur: 100,
+          tdur: 200,
+          ph: 'X',
+          cat: 'disabled-by-default-devtools.timeline',
+          name: 'RunTask',
+          args: {},
+        });
+
+        const data = await preprocessData([
+          ...createBoilerplateEntries(),
+          cpuProfilerSample,
+          randomSample,
+        ]);
+        expect(data).toMatchInlineSnapshot(`
+                Object {
+                  "batchUIDToMeasuresMap": Map {},
+                  "componentMeasures": Array [],
+                  "duration": 0.005,
+                  "flamechart": Array [],
+                  "internalModuleSourceToRanges": Map {},
+                  "laneToLabelMap": Map {
+                    0 => "Sync",
+                    1 => "InputContinuousHydration",
+                    2 => "InputContinuous",
+                    3 => "DefaultHydration",
+                    4 => "Default",
+                    5 => "TransitionHydration",
+                    6 => "Transition",
+                    7 => "Transition",
+                    8 => "Transition",
+                    9 => "Transition",
+                    10 => "Transition",
+                    11 => "Transition",
+                    12 => "Transition",
+                    13 => "Transition",
+                    14 => "Transition",
+                    15 => "Transition",
+                    16 => "Transition",
+                    17 => "Transition",
+                    18 => "Transition",
+                    19 => "Transition",
+                    20 => "Transition",
+                    21 => "Transition",
+                    22 => "Retry",
+                    23 => "Retry",
+                    24 => "Retry",
+                    25 => "Retry",
+                    26 => "Retry",
+                    27 => "SelectiveHydration",
+                    28 => "IdleHydration",
+                    29 => "Idle",
+                    30 => "Offscreen",
+                  },
+                  "laneToReactMeasureMap": Map {
+                    0 => Array [],
+                    1 => Array [],
+                    2 => Array [],
+                    3 => Array [],
+                    4 => Array [],
+                    5 => Array [],
+                    6 => Array [],
+                    7 => Array [],
+                    8 => Array [],
+                    9 => Array [],
+                    10 => Array [],
+                    11 => Array [],
+                    12 => Array [],
+                    13 => Array [],
+                    14 => Array [],
+                    15 => Array [],
+                    16 => Array [],
+                    17 => Array [],
+                    18 => Array [],
+                    19 => Array [],
+                    20 => Array [],
+                    21 => Array [],
+                    22 => Array [],
+                    23 => Array [],
+                    24 => Array [],
+                    25 => Array [],
+                    26 => Array [],
+                    27 => Array [],
+                    28 => Array [],
+                    29 => Array [],
+                    30 => Array [],
+                  },
+                  "nativeEvents": Array [],
+                  "networkMeasures": Array [],
+                  "otherUserTimingMarks": Array [],
+                  "reactVersion": "0.0.0",
+                  "schedulingEvents": Array [],
+                  "snapshotHeight": 0,
+                  "snapshots": Array [],
+                  "startTime": 1,
+                  "suspenseEvents": Array [],
+                  "thrownErrors": Array [],
+                }
+          `);
+      });
 
-    it('should process a sample legacy render sequence', async () => {
-      utils.legacyRender(<div />, document.createElement('div'));
-
-      const data = await preprocessData([
-        ...createBoilerplateEntries(),
-        ...createUserTimingData(clearedMarks),
-      ]);
-      expect(data).toMatchInlineSnapshot(`
-      Object {
-        "batchUIDToMeasuresMap": Map {
-          0 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.01,
-              "lanes": Array [
-                0,
+      it('should process legacy data format (before lane labels were added)', async () => {
+        const cpuProfilerSample = creactCpuProfilerSample();
+
+        // Data below is hard-coded based on an older profile sample.
+        // Should be fine since this is explicitly a legacy-format test.
+        const data = await preprocessData([
+          ...createBoilerplateEntries(),
+          cpuProfilerSample,
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--schedule-render-512-',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--render-start-512',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--render-stop',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--commit-start-512',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--layout-effects-start-512',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--layout-effects-stop',
+          }),
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--commit-stop',
+          }),
+        ]);
+        expect(data).toMatchInlineSnapshot(`
+                Object {
+                  "batchUIDToMeasuresMap": Map {
+                    0 => Array [
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.005,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.006,
+                        "type": "render-idle",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.001,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.006,
+                        "type": "render",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.003,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.008,
+                        "type": "commit",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 1,
+                        "duration": 0.001,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.009,
+                        "type": "layout-effects",
+                      },
+                    ],
+                  },
+                  "componentMeasures": Array [],
+                  "duration": 0.011,
+                  "flamechart": Array [],
+                  "internalModuleSourceToRanges": Map {},
+                  "laneToLabelMap": Map {
+                    0 => "Sync",
+                    1 => "InputContinuousHydration",
+                    2 => "InputContinuous",
+                    3 => "DefaultHydration",
+                    4 => "Default",
+                    5 => "TransitionHydration",
+                    6 => "Transition",
+                    7 => "Transition",
+                    8 => "Transition",
+                    9 => "Transition",
+                    10 => "Transition",
+                    11 => "Transition",
+                    12 => "Transition",
+                    13 => "Transition",
+                    14 => "Transition",
+                    15 => "Transition",
+                    16 => "Transition",
+                    17 => "Transition",
+                    18 => "Transition",
+                    19 => "Transition",
+                    20 => "Transition",
+                    21 => "Transition",
+                    22 => "Retry",
+                    23 => "Retry",
+                    24 => "Retry",
+                    25 => "Retry",
+                    26 => "Retry",
+                    27 => "SelectiveHydration",
+                    28 => "IdleHydration",
+                    29 => "Idle",
+                    30 => "Offscreen",
+                  },
+                  "laneToReactMeasureMap": Map {
+                    0 => Array [],
+                    1 => Array [],
+                    2 => Array [],
+                    3 => Array [],
+                    4 => Array [],
+                    5 => Array [],
+                    6 => Array [],
+                    7 => Array [],
+                    8 => Array [],
+                    9 => Array [
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.005,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.006,
+                        "type": "render-idle",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.001,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.006,
+                        "type": "render",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 0,
+                        "duration": 0.003,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.008,
+                        "type": "commit",
+                      },
+                      Object {
+                        "batchUID": 0,
+                        "depth": 1,
+                        "duration": 0.001,
+                        "lanes": Array [
+                          9,
+                        ],
+                        "timestamp": 0.009,
+                        "type": "layout-effects",
+                      },
+                    ],
+                    10 => Array [],
+                    11 => Array [],
+                    12 => Array [],
+                    13 => Array [],
+                    14 => Array [],
+                    15 => Array [],
+                    16 => Array [],
+                    17 => Array [],
+                    18 => Array [],
+                    19 => Array [],
+                    20 => Array [],
+                    21 => Array [],
+                    22 => Array [],
+                    23 => Array [],
+                    24 => Array [],
+                    25 => Array [],
+                    26 => Array [],
+                    27 => Array [],
+                    28 => Array [],
+                    29 => Array [],
+                    30 => Array [],
+                  },
+                  "nativeEvents": Array [],
+                  "networkMeasures": Array [],
+                  "otherUserTimingMarks": Array [],
+                  "reactVersion": "0.0.0",
+                  "schedulingEvents": Array [
+                    Object {
+                      "lanes": Array [
+                        9,
+                      ],
+                      "timestamp": 0.005,
+                      "type": "schedule-render",
+                      "warning": null,
+                    },
+                  ],
+                  "snapshotHeight": 0,
+                  "snapshots": Array [],
+                  "startTime": 1,
+                  "suspenseEvents": Array [],
+                  "thrownErrors": Array [],
+                }
+          `);
+      });
+
+      it('should process a sample legacy render sequence', async () => {
+        utils.legacyRender(<div />, document.createElement('div'));
+
+        const data = await preprocessData([
+          ...createBoilerplateEntries(),
+          ...createUserTimingData(clearedMarks),
+        ]);
+        expect(data).toMatchInlineSnapshot(`
+          Object {
+            "batchUIDToMeasuresMap": Map {
+              0 => Array [
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.01,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.006,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.012,
+                  "type": "layout-effects",
+                },
               ],
-              "timestamp": 0.004,
-              "type": "render-idle",
             },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.001,
-              "lanes": Array [
-                0,
+            "componentMeasures": Array [],
+            "duration": 0.014,
+            "flamechart": Array [],
+            "internalModuleSourceToRanges": Map {
+              undefined => Array [
+                Array [
+                  Object {
+                    "functionName": "<filtered-file-system-path>",
+                  },
+                  Object {
+                    "functionName": "dule-stop-<filtered-file-system-path>",
+                  },
+                ],
               ],
-              "timestamp": 0.004,
-              "type": "render",
             },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                0,
-              ],
-              "timestamp": 0.006,
-              "type": "commit",
+            "laneToLabelMap": Map {
+              0 => "Sync",
+              1 => "InputContinuousHydration",
+              2 => "InputContinuous",
+              3 => "DefaultHydration",
+              4 => "Default",
+              5 => "TransitionHydration",
+              6 => "Transition",
+              7 => "Transition",
+              8 => "Transition",
+              9 => "Transition",
+              10 => "Transition",
+              11 => "Transition",
+              12 => "Transition",
+              13 => "Transition",
+              14 => "Transition",
+              15 => "Transition",
+              16 => "Transition",
+              17 => "Transition",
+              18 => "Transition",
+              19 => "Transition",
+              20 => "Transition",
+              21 => "Transition",
+              22 => "Retry",
+              23 => "Retry",
+              24 => "Retry",
+              25 => "Retry",
+              26 => "Retry",
+              27 => "SelectiveHydration",
+              28 => "IdleHydration",
+              29 => "Idle",
+              30 => "Offscreen",
             },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                0,
+            "laneToReactMeasureMap": Map {
+              0 => Array [
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.01,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.006,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    0,
+                  ],
+                  "timestamp": 0.012,
+                  "type": "layout-effects",
+                },
               ],
-              "timestamp": 0.012,
-              "type": "layout-effects",
+              1 => Array [],
+              2 => Array [],
+              3 => Array [],
+              4 => Array [],
+              5 => Array [],
+              6 => Array [],
+              7 => Array [],
+              8 => Array [],
+              9 => Array [],
+              10 => Array [],
+              11 => Array [],
+              12 => Array [],
+              13 => Array [],
+              14 => Array [],
+              15 => Array [],
+              16 => Array [],
+              17 => Array [],
+              18 => Array [],
+              19 => Array [],
+              20 => Array [],
+              21 => Array [],
+              22 => Array [],
+              23 => Array [],
+              24 => Array [],
+              25 => Array [],
+              26 => Array [],
+              27 => Array [],
+              28 => Array [],
+              29 => Array [],
+              30 => Array [],
             },
-          ],
-        },
-        "componentMeasures": Array [],
-        "duration": 0.014,
-        "flamechart": Array [],
-        "internalModuleSourceToRanges": Map {
-          undefined => Array [
-            Array [
-              Object {
-                "functionName": "<filtered-file-system-path>",
-              },
+            "nativeEvents": Array [],
+            "networkMeasures": Array [],
+            "otherUserTimingMarks": Array [],
+            "reactVersion": "0.0.0",
+            "schedulingEvents": Array [
               Object {
-                "functionName": "dule-stop-<filtered-file-system-path>",
+                "lanes": Array [
+                  0,
+                ],
+                "timestamp": 0.003,
+                "type": "schedule-render",
+                "warning": null,
               },
             ],
-          ],
-        },
-        "laneToLabelMap": Map {
-          0 => "Sync",
-          1 => "InputContinuousHydration",
-          2 => "InputContinuous",
-          3 => "DefaultHydration",
-          4 => "Default",
-          5 => "TransitionHydration",
-          6 => "Transition",
-          7 => "Transition",
-          8 => "Transition",
-          9 => "Transition",
-          10 => "Transition",
-          11 => "Transition",
-          12 => "Transition",
-          13 => "Transition",
-          14 => "Transition",
-          15 => "Transition",
-          16 => "Transition",
-          17 => "Transition",
-          18 => "Transition",
-          19 => "Transition",
-          20 => "Transition",
-          21 => "Transition",
-          22 => "Retry",
-          23 => "Retry",
-          24 => "Retry",
-          25 => "Retry",
-          26 => "Retry",
-          27 => "SelectiveHydration",
-          28 => "IdleHydration",
-          29 => "Idle",
-          30 => "Offscreen",
-        },
-        "laneToReactMeasureMap": Map {
-          0 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.01,
-              "lanes": Array [
-                0,
-              ],
-              "timestamp": 0.004,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.001,
-              "lanes": Array [
-                0,
-              ],
-              "timestamp": 0.004,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                0,
-              ],
-              "timestamp": 0.006,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                0,
-              ],
-              "timestamp": 0.012,
-              "type": "layout-effects",
-            },
-          ],
-          1 => Array [],
-          2 => Array [],
-          3 => Array [],
-          4 => Array [],
-          5 => Array [],
-          6 => Array [],
-          7 => Array [],
-          8 => Array [],
-          9 => Array [],
-          10 => Array [],
-          11 => Array [],
-          12 => Array [],
-          13 => Array [],
-          14 => Array [],
-          15 => Array [],
-          16 => Array [],
-          17 => Array [],
-          18 => Array [],
-          19 => Array [],
-          20 => Array [],
-          21 => Array [],
-          22 => Array [],
-          23 => Array [],
-          24 => Array [],
-          25 => Array [],
-          26 => Array [],
-          27 => Array [],
-          28 => Array [],
-          29 => Array [],
-          30 => Array [],
-        },
-        "nativeEvents": Array [],
-        "networkMeasures": Array [],
-        "otherUserTimingMarks": Array [],
-        "reactVersion": "0.0.0",
-        "schedulingEvents": Array [
-          Object {
-            "lanes": Array [
-              0,
-            ],
-            "timestamp": 0.003,
-            "type": "schedule-render",
-            "warning": null,
-          },
-        ],
-        "snapshotHeight": 0,
-        "snapshots": Array [],
-        "startTime": 4,
-        "suspenseEvents": Array [],
-        "thrownErrors": Array [],
-      }
-    `);
-    });
-
-    it('should process a sample createRoot render sequence', async () => {
-      function App() {
-        const [didMount, setDidMount] = React.useState(false);
-        React.useEffect(() => {
-          if (!didMount) {
-            setDidMount(true);
+            "snapshotHeight": 0,
+            "snapshots": Array [],
+            "startTime": 4,
+            "suspenseEvents": Array [],
+            "thrownErrors": Array [],
           }
-        });
-        return true;
-      }
+        `);
+      });
 
-      const root = ReactDOM.createRoot(document.createElement('div'));
-      utils.act(() => root.render(<App />));
-
-      const data = await preprocessData([
-        ...createBoilerplateEntries(),
-        ...createUserTimingData(clearedMarks),
-      ]);
-      expect(data).toMatchInlineSnapshot(`
-      Object {
-        "batchUIDToMeasuresMap": Map {
-          0 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.012,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.004,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.004,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.008,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.014,
-              "type": "layout-effects",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.004,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.017,
-              "type": "passive-effects",
-            },
-          ],
-          1 => Array [
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.012,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.022,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.022,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.026,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                4,
+      it('should process a sample createRoot render sequence', async () => {
+        function App() {
+          const [didMount, setDidMount] = React.useState(false);
+          React.useEffect(() => {
+            if (!didMount) {
+              setDidMount(true);
+            }
+          });
+          return true;
+        }
+
+        const root = ReactDOM.createRoot(document.createElement('div'));
+        utils.act(() => root.render(<App />));
+
+        const data = await preprocessData([
+          ...createBoilerplateEntries(),
+          ...createUserTimingData(clearedMarks),
+        ]);
+        expect(data).toMatchInlineSnapshot(`
+          Object {
+            "batchUIDToMeasuresMap": Map {
+              0 => Array [
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.012,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.008,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.014,
+                  "type": "layout-effects",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.004,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.017,
+                  "type": "passive-effects",
+                },
               ],
-              "timestamp": 0.032,
-              "type": "layout-effects",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
+              1 => Array [
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.012,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.022,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.022,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.026,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.032,
+                  "type": "layout-effects",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.035,
+                  "type": "passive-effects",
+                },
               ],
-              "timestamp": 0.035,
-              "type": "passive-effects",
             },
-          ],
-        },
-        "componentMeasures": Array [
-          Object {
-            "componentName": "App",
-            "duration": 0.001,
-            "timestamp": 0.005,
-            "type": "render",
-            "warning": null,
-          },
-          Object {
-            "componentName": "App",
-            "duration": 0.002,
-            "timestamp": 0.018,
-            "type": "passive-effect-mount",
-            "warning": null,
-          },
-          Object {
-            "componentName": "App",
-            "duration": 0.001,
-            "timestamp": 0.023,
-            "type": "render",
-            "warning": null,
-          },
-          Object {
-            "componentName": "App",
-            "duration": 0.001,
-            "timestamp": 0.036,
-            "type": "passive-effect-mount",
-            "warning": null,
-          },
-        ],
-        "duration": 0.038,
-        "flamechart": Array [],
-        "internalModuleSourceToRanges": Map {
-          undefined => Array [
-            Array [
+            "componentMeasures": Array [
               Object {
-                "functionName": "<filtered-file-system-path>",
+                "componentName": "App",
+                "duration": 0.001,
+                "timestamp": 0.005,
+                "type": "render",
+                "warning": null,
               },
               Object {
-                "functionName": "dule-stop-<filtered-file-system-path>",
+                "componentName": "App",
+                "duration": 0.002,
+                "timestamp": 0.018,
+                "type": "passive-effect-mount",
+                "warning": null,
+              },
+              Object {
+                "componentName": "App",
+                "duration": 0.001,
+                "timestamp": 0.023,
+                "type": "render",
+                "warning": null,
+              },
+              Object {
+                "componentName": "App",
+                "duration": 0.001,
+                "timestamp": 0.036,
+                "type": "passive-effect-mount",
+                "warning": null,
               },
             ],
-          ],
-        },
-        "laneToLabelMap": Map {
-          0 => "Sync",
-          1 => "InputContinuousHydration",
-          2 => "InputContinuous",
-          3 => "DefaultHydration",
-          4 => "Default",
-          5 => "TransitionHydration",
-          6 => "Transition",
-          7 => "Transition",
-          8 => "Transition",
-          9 => "Transition",
-          10 => "Transition",
-          11 => "Transition",
-          12 => "Transition",
-          13 => "Transition",
-          14 => "Transition",
-          15 => "Transition",
-          16 => "Transition",
-          17 => "Transition",
-          18 => "Transition",
-          19 => "Transition",
-          20 => "Transition",
-          21 => "Transition",
-          22 => "Retry",
-          23 => "Retry",
-          24 => "Retry",
-          25 => "Retry",
-          26 => "Retry",
-          27 => "SelectiveHydration",
-          28 => "IdleHydration",
-          29 => "Idle",
-          30 => "Offscreen",
-        },
-        "laneToReactMeasureMap": Map {
-          0 => Array [],
-          1 => Array [],
-          2 => Array [],
-          3 => Array [],
-          4 => Array [
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.012,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.004,
-              "type": "render-idle",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.004,
-              "type": "render",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.008,
-              "type": "commit",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.014,
-              "type": "layout-effects",
-            },
-            Object {
-              "batchUID": 0,
-              "depth": 0,
-              "duration": 0.004,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.017,
-              "type": "passive-effects",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.012,
-              "lanes": Array [
-                4,
+            "duration": 0.038,
+            "flamechart": Array [],
+            "internalModuleSourceToRanges": Map {
+              undefined => Array [
+                Array [
+                  Object {
+                    "functionName": "<filtered-file-system-path>",
+                  },
+                  Object {
+                    "functionName": "dule-stop-<filtered-file-system-path>",
+                  },
+                ],
               ],
-              "timestamp": 0.022,
-              "type": "render-idle",
             },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.022,
-              "type": "render",
+            "laneToLabelMap": Map {
+              0 => "Sync",
+              1 => "InputContinuousHydration",
+              2 => "InputContinuous",
+              3 => "DefaultHydration",
+              4 => "Default",
+              5 => "TransitionHydration",
+              6 => "Transition",
+              7 => "Transition",
+              8 => "Transition",
+              9 => "Transition",
+              10 => "Transition",
+              11 => "Transition",
+              12 => "Transition",
+              13 => "Transition",
+              14 => "Transition",
+              15 => "Transition",
+              16 => "Transition",
+              17 => "Transition",
+              18 => "Transition",
+              19 => "Transition",
+              20 => "Transition",
+              21 => "Transition",
+              22 => "Retry",
+              23 => "Retry",
+              24 => "Retry",
+              25 => "Retry",
+              26 => "Retry",
+              27 => "SelectiveHydration",
+              28 => "IdleHydration",
+              29 => "Idle",
+              30 => "Offscreen",
             },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.008,
-              "lanes": Array [
-                4,
+            "laneToReactMeasureMap": Map {
+              0 => Array [],
+              1 => Array [],
+              2 => Array [],
+              3 => Array [],
+              4 => Array [
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.012,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.004,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.008,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.014,
+                  "type": "layout-effects",
+                },
+                Object {
+                  "batchUID": 0,
+                  "depth": 0,
+                  "duration": 0.004,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.017,
+                  "type": "passive-effects",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.012,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.022,
+                  "type": "render-idle",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.022,
+                  "type": "render",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.008,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.026,
+                  "type": "commit",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 1,
+                  "duration": 0.001,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.032,
+                  "type": "layout-effects",
+                },
+                Object {
+                  "batchUID": 1,
+                  "depth": 0,
+                  "duration": 0.003,
+                  "lanes": Array [
+                    4,
+                  ],
+                  "timestamp": 0.035,
+                  "type": "passive-effects",
+                },
               ],
-              "timestamp": 0.026,
-              "type": "commit",
+              5 => Array [],
+              6 => Array [],
+              7 => Array [],
+              8 => Array [],
+              9 => Array [],
+              10 => Array [],
+              11 => Array [],
+              12 => Array [],
+              13 => Array [],
+              14 => Array [],
+              15 => Array [],
+              16 => Array [],
+              17 => Array [],
+              18 => Array [],
+              19 => Array [],
+              20 => Array [],
+              21 => Array [],
+              22 => Array [],
+              23 => Array [],
+              24 => Array [],
+              25 => Array [],
+              26 => Array [],
+              27 => Array [],
+              28 => Array [],
+              29 => Array [],
+              30 => Array [],
             },
-            Object {
-              "batchUID": 1,
-              "depth": 1,
-              "duration": 0.001,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.032,
-              "type": "layout-effects",
-            },
-            Object {
-              "batchUID": 1,
-              "depth": 0,
-              "duration": 0.003,
-              "lanes": Array [
-                4,
-              ],
-              "timestamp": 0.035,
-              "type": "passive-effects",
-            },
-          ],
-          5 => Array [],
-          6 => Array [],
-          7 => Array [],
-          8 => Array [],
-          9 => Array [],
-          10 => Array [],
-          11 => Array [],
-          12 => Array [],
-          13 => Array [],
-          14 => Array [],
-          15 => Array [],
-          16 => Array [],
-          17 => Array [],
-          18 => Array [],
-          19 => Array [],
-          20 => Array [],
-          21 => Array [],
-          22 => Array [],
-          23 => Array [],
-          24 => Array [],
-          25 => Array [],
-          26 => Array [],
-          27 => Array [],
-          28 => Array [],
-          29 => Array [],
-          30 => Array [],
-        },
-        "nativeEvents": Array [],
-        "networkMeasures": Array [],
-        "otherUserTimingMarks": Array [],
-        "reactVersion": "0.0.0",
-        "schedulingEvents": Array [
-          Object {
-            "lanes": Array [
-              4,
-            ],
-            "timestamp": 0.003,
-            "type": "schedule-render",
-            "warning": null,
-          },
-          Object {
-            "componentName": "App",
-            "lanes": Array [
-              4,
+            "nativeEvents": Array [],
+            "networkMeasures": Array [],
+            "otherUserTimingMarks": Array [],
+            "reactVersion": "0.0.0",
+            "schedulingEvents": Array [
+              Object {
+                "lanes": Array [
+                  4,
+                ],
+                "timestamp": 0.003,
+                "type": "schedule-render",
+                "warning": null,
+              },
+              Object {
+                "componentName": "App",
+                "lanes": Array [
+                  4,
+                ],
+                "timestamp": 0.019,
+                "type": "schedule-state-update",
+                "warning": null,
+              },
             ],
-            "timestamp": 0.019,
-            "type": "schedule-state-update",
-            "warning": null,
-          },
-        ],
-        "snapshotHeight": 0,
-        "snapshots": Array [],
-        "startTime": 4,
-        "suspenseEvents": Array [],
-        "thrownErrors": Array [],
-      }
-    `);
-    });
-
-    it('should error if events and measures are incomplete', async () => {
-      const container = document.createElement('div');
-      utils.legacyRender(<div />, container);
-
-      const invalidMarks = clearedMarks.filter(
-        mark => !mark.includes('render-stop'),
-      );
-      const invalidUserTimingData = createUserTimingData(invalidMarks);
-
-      const error = spyOn(console, 'error');
-      preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
-      expect(error).toHaveBeenCalled();
-    });
-
-    it('should error if work is completed without being started', async () => {
-      const container = document.createElement('div');
-      utils.legacyRender(<div />, container);
+            "snapshotHeight": 0,
+            "snapshots": Array [],
+            "startTime": 4,
+            "suspenseEvents": Array [],
+            "thrownErrors": Array [],
+          }
+        `);
+      });
 
-      const invalidMarks = clearedMarks.filter(
-        mark => !mark.includes('render-start'),
-      );
-      const invalidUserTimingData = createUserTimingData(invalidMarks);
+      it('should error if events and measures are incomplete', async () => {
+        const container = document.createElement('div');
+        utils.legacyRender(<div />, container);
+
+        const invalidMarks = clearedMarks.filter(
+          mark => !mark.includes('render-stop'),
+        );
+        const invalidUserTimingData = createUserTimingData(invalidMarks);
+
+        const error = spyOn(console, 'error');
+        preprocessData([
+          ...createBoilerplateEntries(),
+          ...invalidUserTimingData,
+        ]);
+        expect(error).toHaveBeenCalled();
+      });
 
-      const error = spyOn(console, 'error');
-      preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
-      expect(error).toHaveBeenCalled();
-    });
+      it('should error if work is completed without being started', async () => {
+        const container = document.createElement('div');
+        utils.legacyRender(<div />, container);
+
+        const invalidMarks = clearedMarks.filter(
+          mark => !mark.includes('render-start'),
+        );
+        const invalidUserTimingData = createUserTimingData(invalidMarks);
+
+        const error = spyOn(console, 'error');
+        preprocessData([
+          ...createBoilerplateEntries(),
+          ...invalidUserTimingData,
+        ]);
+        expect(error).toHaveBeenCalled();
+      });
 
-    it('should populate other user timing marks', async () => {
-      const userTimingData = createUserTimingData([]);
-      userTimingData.push(
-        createUserTimingEntry({
-          args: {},
-          cat: 'blink.user_timing',
-          id: '0xcdf75f7c',
-          name: 'VCWithoutImage: root',
-          ph: 'n',
-          scope: 'blink.user_timing',
-        }),
-      );
-      userTimingData.push(
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: '--a-mark-that-looks-like-one-of-ours',
-          ph: 'R',
-        }),
-      );
-      userTimingData.push(
-        createUserTimingEntry({
-          cat: 'blink.user_timing',
-          name: 'Some other mark',
-          ph: 'R',
-        }),
-      );
-
-      const data = await preprocessData([
-        ...createBoilerplateEntries(),
-        ...userTimingData,
-      ]);
-      expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
-      Array [
-        Object {
-          "name": "VCWithoutImage: root",
-          "timestamp": 0.003,
-        },
-        Object {
-          "name": "--a-mark-that-looks-like-one-of-ours",
-          "timestamp": 0.004,
-        },
-        Object {
-          "name": "Some other mark",
-          "timestamp": 0.005,
-        },
-      ]
-    `);
-    });
+      it('should populate other user timing marks', async () => {
+        const userTimingData = createUserTimingData([]);
+        userTimingData.push(
+          createUserTimingEntry({
+            args: {},
+            cat: 'blink.user_timing',
+            id: '0xcdf75f7c',
+            name: 'VCWithoutImage: root',
+            ph: 'n',
+            scope: 'blink.user_timing',
+          }),
+        );
+        userTimingData.push(
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: '--a-mark-that-looks-like-one-of-ours',
+            ph: 'R',
+          }),
+        );
+        userTimingData.push(
+          createUserTimingEntry({
+            cat: 'blink.user_timing',
+            name: 'Some other mark',
+            ph: 'R',
+          }),
+        );
+
+        const data = await preprocessData([
+          ...createBoilerplateEntries(),
+          ...userTimingData,
+        ]);
+        expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
+                Array [
+                  Object {
+                    "name": "VCWithoutImage: root",
+                    "timestamp": 0.003,
+                  },
+                  Object {
+                    "name": "--a-mark-that-looks-like-one-of-ours",
+                    "timestamp": 0.004,
+                  },
+                  Object {
+                    "name": "Some other mark",
+                    "timestamp": 0.005,
+                  },
+                ]
+          `);
+      });
 
-    it('should include a suspended resource "displayName" if one is set', async () => {
-      let promise = null;
-      let resolvedValue = null;
-      function readValue(value) {
-        if (resolvedValue !== null) {
-          return resolvedValue;
-        } else if (promise === null) {
-          promise = Promise.resolve(true).then(() => {
-            resolvedValue = value;
-          });
-          promise.displayName = 'Testing displayName';
+      it('should include a suspended resource "displayName" if one is set', async () => {
+        let promise = null;
+        let resolvedValue = null;
+        function readValue(value) {
+          if (resolvedValue !== null) {
+            return resolvedValue;
+          } else if (promise === null) {
+            promise = Promise.resolve(true).then(() => {
+              resolvedValue = value;
+            });
+            promise.displayName = 'Testing displayName';
+          }
+          throw promise;
         }
-        throw promise;
-      }
 
-      function Component() {
-        const value = readValue(123);
-        return value;
-      }
+        function Component() {
+          const value = readValue(123);
+          return value;
+        }
 
-      const testMarks = [creactCpuProfilerSample()];
+        const testMarks = [creactCpuProfilerSample()];
 
-      const root = ReactDOM.createRoot(document.createElement('div'));
-      utils.act(() =>
-        root.render(
-          <React.Suspense fallback="Loading...">
-            <Component />
-          </React.Suspense>,
-        ),
-      );
+        const root = ReactDOM.createRoot(document.createElement('div'));
+        utils.act(() =>
+          root.render(
+            <React.Suspense fallback="Loading...">
+              <Component />
+            </React.Suspense>,
+          ),
+        );
 
-      testMarks.push(...createUserTimingData(clearedMarks));
+        testMarks.push(...createUserTimingData(clearedMarks));
 
-      let data;
-      await utils.actAsync(async () => {
-        data = await preprocessData(testMarks);
+        let data;
+        await utils.actAsync(async () => {
+          data = await preprocessData(testMarks);
+        });
+        expect(data.suspenseEvents).toHaveLength(1);
+        expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
       });
-      expect(data.suspenseEvents).toHaveLength(1);
-      expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
-    });
 
-    describe('warnings', () => {
-      describe('long event handlers', () => {
-        it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
-          function App() {
-            return null;
-          }
+      describe('warnings', () => {
+        describe('long event handlers', () => {
+          it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
+            function App() {
+              return null;
+            }
 
-          const testMarks = [
-            creactCpuProfilerSample(),
-            ...createBoilerplateEntries(),
-            createNativeEventEntry('click', 5),
-          ];
+            const testMarks = [
+              creactCpuProfilerSample(),
+              ...createBoilerplateEntries(),
+              createNativeEventEntry('click', 5),
+            ];
 
-          clearPendingMarks();
+            clearPendingMarks();
 
-          utils.legacyRender(<App />, document.createElement('div'));
+            utils.legacyRender(<App />, document.createElement('div'));
 
-          testMarks.push(...createUserTimingData(clearedMarks));
+            testMarks.push(...createUserTimingData(clearedMarks));
 
-          const data = await preprocessData(testMarks);
-          const event = data.nativeEvents.find(({type}) => type === 'click');
-          expect(event.warning).toBe(null);
-        });
+            const data = await preprocessData(testMarks);
+            const event = data.nativeEvents.find(({type}) => type === 'click');
+            expect(event.warning).toBe(null);
+          });
 
-        it('should not warn about long events if the cause was non-React JavaScript', async () => {
-          function App() {
-            return null;
-          }
+          it('should not warn about long events if the cause was non-React JavaScript', async () => {
+            function App() {
+              return null;
+            }
 
-          const testMarks = [
-            creactCpuProfilerSample(),
-            ...createBoilerplateEntries(),
-            createNativeEventEntry('click', 25000),
-          ];
+            const testMarks = [
+              creactCpuProfilerSample(),
+              ...createBoilerplateEntries(),
+              createNativeEventEntry('click', 25000),
+            ];
 
-          startTime += 2000;
+            startTime += 2000;
 
-          clearPendingMarks();
+            clearPendingMarks();
 
-          utils.legacyRender(<App />, document.createElement('div'));
+            utils.legacyRender(<App />, document.createElement('div'));
 
-          testMarks.push(...createUserTimingData(clearedMarks));
+            testMarks.push(...createUserTimingData(clearedMarks));
 
-          const data = await preprocessData(testMarks);
-          const event = data.nativeEvents.find(({type}) => type === 'click');
-          expect(event.warning).toBe(null);
-        });
+            const data = await preprocessData(testMarks);
+            const event = data.nativeEvents.find(({type}) => type === 'click');
+            expect(event.warning).toBe(null);
+          });
 
-        it('should warn when React scedules a long (sync) update inside of an event', async () => {
-          function App() {
-            return null;
-          }
+          it('should warn when React scedules a long (sync) update inside of an event', async () => {
+            function App() {
+              return null;
+            }
 
-          const testMarks = [
-            creactCpuProfilerSample(),
-            ...createBoilerplateEntries(),
-            createNativeEventEntry('click', 25000),
-          ];
+            const testMarks = [
+              creactCpuProfilerSample(),
+              ...createBoilerplateEntries(),
+              createNativeEventEntry('click', 25000),
+            ];
 
-          clearPendingMarks();
+            clearPendingMarks();
 
-          utils.legacyRender(<App />, document.createElement('div'));
+            utils.legacyRender(<App />, document.createElement('div'));
 
-          clearedMarks.forEach(markName => {
-            if (markName === '--render-stop') {
-              // Fake a long running render
-              startTime += 20000;
-            }
+            clearedMarks.forEach(markName => {
+              if (markName === '--render-stop') {
+                // Fake a long running render
+                startTime += 20000;
+              }
 
-            testMarks.push({
-              pid: ++pid,
-              tid: ++tid,
-              ts: ++startTime,
-              args: {data: {}},
-              cat: 'blink.user_timing',
-              name: markName,
-              ph: 'R',
+              testMarks.push({
+                pid: ++pid,
+                tid: ++tid,
+                ts: ++startTime,
+                args: {data: {}},
+                cat: 'blink.user_timing',
+                name: markName,
+                ph: 'R',
+              });
             });
-          });
 
-          const data = await preprocessData(testMarks);
-          const event = data.nativeEvents.find(({type}) => type === 'click');
-          expect(event.warning).toMatchInlineSnapshot(
-            `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
-          );
-        });
+            const data = await preprocessData(testMarks);
+            const event = data.nativeEvents.find(({type}) => type === 'click');
+            expect(event.warning).toMatchInlineSnapshot(
+              `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
+            );
+          });
 
-        it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
-          function Yield({id, value}) {
-            Scheduler.unstable_yieldValue(`${id}:${value}`);
-            return null;
-          }
+          it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
+            function Yield({id, value}) {
+              Scheduler.unstable_yieldValue(`${id}:${value}`);
+              return null;
+            }
 
-          const testMarks = [
-            creactCpuProfilerSample(),
-            ...createBoilerplateEntries(),
-          ];
-
-          // Advance the clock by some arbitrary amount.
-          startTime += 50000;
-
-          const root = ReactDOM.createRoot(document.createElement('div'));
-
-          // Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
-          global.IS_REACT_ACT_ENVIRONMENT = false;
-          React.startTransition(() => {
-            // Start rendering an async update (but don't finish).
-            root.render(
-              <>
-                <Yield id="A" value={1} />
-                <Yield id="B" value={1} />
-              </>,
-            );
-            expect(Scheduler).toFlushAndYieldThrough(['A:1']);
+            const testMarks = [
+              creactCpuProfilerSample(),
+              ...createBoilerplateEntries(),
+            ];
 
-            testMarks.push(...createUserTimingData(clearedMarks));
-            clearPendingMarks();
+            // Advance the clock by some arbitrary amount.
+            startTime += 50000;
 
-            // Advance the clock some more to make the pending React update seem long.
-            startTime += 20000;
+            const root = ReactDOM.createRoot(document.createElement('div'));
 
-            // Fake a long "click" event in the middle
-            // and schedule a sync update that will also flush the previous work.
-            testMarks.push(createNativeEventEntry('click', 25000));
-            ReactDOM.flushSync(() => {
+            // Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
+            global.IS_REACT_ACT_ENVIRONMENT = false;
+            React.startTransition(() => {
+              // Start rendering an async update (but don't finish).
               root.render(
                 <>
-                  <Yield id="A" value={2} />
-                  <Yield id="B" value={2} />
+                  <Yield id="A" value={1} />
+                  <Yield id="B" value={1} />
                 </>,
               );
-            });
-          });
+              expect(Scheduler).toFlushAndYieldThrough(['A:1']);
 
-          expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
+              testMarks.push(...createUserTimingData(clearedMarks));
+              clearPendingMarks();
 
-          testMarks.push(...createUserTimingData(clearedMarks));
-
-          const data = await preprocessData(testMarks);
-          const event = data.nativeEvents.find(({type}) => type === 'click');
-          expect(event.warning).toBe(null);
-        });
-      });
-
-      describe('nested updates', () => {
-        it('should not warn about short nested (state) updates during layout effects', async () => {
-          function Component() {
-            const [didMount, setDidMount] = React.useState(false);
-            Scheduler.unstable_yieldValue(
-              `Component ${didMount ? 'update' : 'mount'}`,
-            );
-            React.useLayoutEffect(() => {
-              setDidMount(true);
-            }, []);
-            return didMount;
-          }
+              // Advance the clock some more to make the pending React update seem long.
+              startTime += 20000;
 
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
-          });
+              // Fake a long "click" event in the middle
+              // and schedule a sync update that will also flush the previous work.
+              testMarks.push(createNativeEventEntry('click', 25000));
+              ReactDOM.flushSync(() => {
+                root.render(
+                  <>
+                    <Yield id="A" value={2} />
+                    <Yield id="B" value={2} />
+                  </>,
+                );
+              });
+            });
 
-          expect(Scheduler).toHaveYielded([
-            'Component mount',
-            'Component update',
-          ]);
+            expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
 
-          const data = await preprocessData([
-            ...createBoilerplateEntries(),
-            ...createUserTimingData(clearedMarks),
-          ]);
+            testMarks.push(...createUserTimingData(clearedMarks));
 
-          const event = data.schedulingEvents.find(
-            ({type}) => type === 'schedule-state-update',
-          );
-          expect(event.warning).toBe(null);
+            const data = await preprocessData(testMarks);
+            const event = data.nativeEvents.find(({type}) => type === 'click');
+            expect(event.warning).toBe(null);
+          });
         });
 
-        it('should not warn about short (forced) updates during layout effects', async () => {
-          class Component extends React.Component {
-            _didMount: boolean = false;
-            componentDidMount() {
-              this._didMount = true;
-              this.forceUpdate();
-            }
-            render() {
+        describe('nested updates', () => {
+          it('should not warn about short nested (state) updates during layout effects', async () => {
+            function Component() {
+              const [didMount, setDidMount] = React.useState(false);
               Scheduler.unstable_yieldValue(
-                `Component ${this._didMount ? 'update' : 'mount'}`,
+                `Component ${didMount ? 'update' : 'mount'}`,
               );
-              return null;
+              React.useLayoutEffect(() => {
+                setDidMount(true);
+              }, []);
+              return didMount;
             }
-          }
-
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
-          });
 
-          expect(Scheduler).toHaveYielded([
-            'Component mount',
-            'Component update',
-          ]);
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
+            });
 
-          const data = await preprocessData([
-            ...createBoilerplateEntries(),
-            ...createUserTimingData(clearedMarks),
-          ]);
+            expect(Scheduler).toHaveYielded([
+              'Component mount',
+              'Component update',
+            ]);
 
-          const event = data.schedulingEvents.find(
-            ({type}) => type === 'schedule-force-update',
-          );
-          expect(event.warning).toBe(null);
-        });
+            const data = await preprocessData([
+              ...createBoilerplateEntries(),
+              ...createUserTimingData(clearedMarks),
+            ]);
 
-        it('should warn about long nested (state) updates during layout effects', async () => {
-          function Component() {
-            const [didMount, setDidMount] = React.useState(false);
-            Scheduler.unstable_yieldValue(
-              `Component ${didMount ? 'update' : 'mount'}`,
+            const event = data.schedulingEvents.find(
+              ({type}) => type === 'schedule-state-update',
             );
-            // Fake a long render
-            startTime += 20000;
-            React.useLayoutEffect(() => {
-              setDidMount(true);
-            }, []);
-            return didMount;
-          }
-
-          const cpuProfilerSample = creactCpuProfilerSample();
-
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
+            expect(event.warning).toBe(null);
           });
 
-          expect(Scheduler).toHaveYielded([
-            'Component mount',
-            'Component update',
-          ]);
-
-          const testMarks = [];
-          clearedMarks.forEach(markName => {
-            if (markName === '--component-render-start-Component') {
-              // Fake a long running render
-              startTime += 20000;
+          it('should not warn about short (forced) updates during layout effects', async () => {
+            class Component extends React.Component {
+              _didMount: boolean = false;
+              componentDidMount() {
+                this._didMount = true;
+                this.forceUpdate();
+              }
+              render() {
+                Scheduler.unstable_yieldValue(
+                  `Component ${this._didMount ? 'update' : 'mount'}`,
+                );
+                return null;
+              }
             }
 
-            testMarks.push({
-              pid: ++pid,
-              tid: ++tid,
-              ts: ++startTime,
-              args: {data: {}},
-              cat: 'blink.user_timing',
-              name: markName,
-              ph: 'R',
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
             });
-          });
 
-          const data = await preprocessData([
-            cpuProfilerSample,
-            ...createBoilerplateEntries(),
-            ...testMarks,
-          ]);
-
-          const event = data.schedulingEvents.find(
-            ({type}) => type === 'schedule-state-update',
-          );
-          expect(event.warning).toMatchInlineSnapshot(
-            `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
-          );
-        });
+            expect(Scheduler).toHaveYielded([
+              'Component mount',
+              'Component update',
+            ]);
 
-        it('should warn about long nested (forced) updates during layout effects', async () => {
-          class Component extends React.Component {
-            _didMount: boolean = false;
-            componentDidMount() {
-              this._didMount = true;
-              this.forceUpdate();
-            }
-            render() {
+            const data = await preprocessData([
+              ...createBoilerplateEntries(),
+              ...createUserTimingData(clearedMarks),
+            ]);
+
+            const event = data.schedulingEvents.find(
+              ({type}) => type === 'schedule-force-update',
+            );
+            expect(event.warning).toBe(null);
+          });
+
+          it('should warn about long nested (state) updates during layout effects', async () => {
+            function Component() {
+              const [didMount, setDidMount] = React.useState(false);
               Scheduler.unstable_yieldValue(
-                `Component ${this._didMount ? 'update' : 'mount'}`,
+                `Component ${didMount ? 'update' : 'mount'}`,
               );
-              return null;
+              // Fake a long render
+              startTime += 20000;
+              React.useLayoutEffect(() => {
+                setDidMount(true);
+              }, []);
+              return didMount;
             }
-          }
 
-          const cpuProfilerSample = creactCpuProfilerSample();
+            const cpuProfilerSample = creactCpuProfilerSample();
 
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
-          });
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
+            });
 
-          expect(Scheduler).toHaveYielded([
-            'Component mount',
-            'Component update',
-          ]);
+            expect(Scheduler).toHaveYielded([
+              'Component mount',
+              'Component update',
+            ]);
 
-          const testMarks = [];
-          clearedMarks.forEach(markName => {
-            if (markName === '--component-render-start-Component') {
-              // Fake a long running render
-              startTime += 20000;
-            }
+            const testMarks = [];
+            clearedMarks.forEach(markName => {
+              if (markName === '--component-render-start-Component') {
+                // Fake a long running render
+                startTime += 20000;
+              }
 
-            testMarks.push({
-              pid: ++pid,
-              tid: ++tid,
-              ts: ++startTime,
-              args: {data: {}},
-              cat: 'blink.user_timing',
-              name: markName,
-              ph: 'R',
+              testMarks.push({
+                pid: ++pid,
+                tid: ++tid,
+                ts: ++startTime,
+                args: {data: {}},
+                cat: 'blink.user_timing',
+                name: markName,
+                ph: 'R',
+              });
             });
-          });
 
-          const data = await preprocessData([
-            cpuProfilerSample,
-            ...createBoilerplateEntries(),
-            ...testMarks,
-          ]);
-
-          const event = data.schedulingEvents.find(
-            ({type}) => type === 'schedule-force-update',
-          );
-          expect(event.warning).toMatchInlineSnapshot(
-            `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
-          );
-        });
+            const data = await preprocessData([
+              cpuProfilerSample,
+              ...createBoilerplateEntries(),
+              ...testMarks,
+            ]);
 
-        it('should not warn about transition updates scheduled during commit phase', async () => {
-          function Component() {
-            const [value, setValue] = React.useState(0);
-            // eslint-disable-next-line no-unused-vars
-            const [isPending, startTransition] = React.useTransition();
-
-            Scheduler.unstable_yieldValue(
-              `Component rendered with value ${value}`,
+            const event = data.schedulingEvents.find(
+              ({type}) => type === 'schedule-state-update',
+            );
+            expect(event.warning).toMatchInlineSnapshot(
+              `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
             );
+          });
 
-            // Fake a long render
-            if (value !== 0) {
-              Scheduler.unstable_yieldValue('Long render');
-              startTime += 20000;
+          it('should warn about long nested (forced) updates during layout effects', async () => {
+            class Component extends React.Component {
+              _didMount: boolean = false;
+              componentDidMount() {
+                this._didMount = true;
+                this.forceUpdate();
+              }
+              render() {
+                Scheduler.unstable_yieldValue(
+                  `Component ${this._didMount ? 'update' : 'mount'}`,
+                );
+                return null;
+              }
             }
 
-            React.useLayoutEffect(() => {
-              startTransition(() => {
-                setValue(1);
-              });
-            }, []);
+            const cpuProfilerSample = creactCpuProfilerSample();
 
-            return value;
-          }
-
-          const cpuProfilerSample = creactCpuProfilerSample();
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
+            });
 
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
-          });
+            expect(Scheduler).toHaveYielded([
+              'Component mount',
+              'Component update',
+            ]);
 
-          expect(Scheduler).toHaveYielded([
-            'Component rendered with value 0',
-            'Component rendered with value 0',
-            'Component rendered with value 1',
-            'Long render',
-          ]);
-
-          const testMarks = [];
-          clearedMarks.forEach(markName => {
-            if (markName === '--component-render-start-Component') {
-              // Fake a long running render
-              startTime += 20000;
-            }
+            const testMarks = [];
+            clearedMarks.forEach(markName => {
+              if (markName === '--component-render-start-Component') {
+                // Fake a long running render
+                startTime += 20000;
+              }
 
-            testMarks.push({
-              pid: ++pid,
-              tid: ++tid,
-              ts: ++startTime,
-              args: {data: {}},
-              cat: 'blink.user_timing',
-              name: markName,
-              ph: 'R',
+              testMarks.push({
+                pid: ++pid,
+                tid: ++tid,
+                ts: ++startTime,
+                args: {data: {}},
+                cat: 'blink.user_timing',
+                name: markName,
+                ph: 'R',
+              });
             });
-          });
 
-          const data = await preprocessData([
-            cpuProfilerSample,
-            ...createBoilerplateEntries(),
-            ...testMarks,
-          ]);
+            const data = await preprocessData([
+              cpuProfilerSample,
+              ...createBoilerplateEntries(),
+              ...testMarks,
+            ]);
 
-          data.schedulingEvents.forEach(event => {
-            expect(event.warning).toBeNull();
+            const event = data.schedulingEvents.find(
+              ({type}) => type === 'schedule-force-update',
+            );
+            expect(event.warning).toMatchInlineSnapshot(
+              `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
+            );
           });
-        });
 
-        it('should not warn about deferred value updates scheduled during commit phase', async () => {
-          function Component() {
-            const [value, setValue] = React.useState(0);
-            const deferredValue = React.useDeferredValue(value);
+          it('should not warn about transition updates scheduled during commit phase', async () => {
+            function Component() {
+              const [value, setValue] = React.useState(0);
+              // eslint-disable-next-line no-unused-vars
+              const [isPending, startTransition] = React.useTransition();
 
-            Scheduler.unstable_yieldValue(
-              `Component rendered with value ${value} and deferredValue ${deferredValue}`,
-            );
+              Scheduler.unstable_yieldValue(
+                `Component rendered with value ${value}`,
+              );
 
-            // Fake a long render
-            if (deferredValue !== 0) {
-              Scheduler.unstable_yieldValue('Long render');
-              startTime += 20000;
+              // Fake a long render
+              if (value !== 0) {
+                Scheduler.unstable_yieldValue('Long render');
+                startTime += 20000;
+              }
+
+              React.useLayoutEffect(() => {
+                startTransition(() => {
+                  setValue(1);
+                });
+              }, []);
+
+              return value;
             }
 
-            React.useLayoutEffect(() => {
-              setValue(1);
-            }, []);
+            const cpuProfilerSample = creactCpuProfilerSample();
 
-            return value + deferredValue;
-          }
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
+            });
 
-          const cpuProfilerSample = creactCpuProfilerSample();
+            expect(Scheduler).toHaveYielded([
+              'Component rendered with value 0',
+              'Component rendered with value 0',
+              'Component rendered with value 1',
+              'Long render',
+            ]);
+
+            const testMarks = [];
+            clearedMarks.forEach(markName => {
+              if (markName === '--component-render-start-Component') {
+                // Fake a long running render
+                startTime += 20000;
+              }
 
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() => {
-            root.render(<Component />);
+              testMarks.push({
+                pid: ++pid,
+                tid: ++tid,
+                ts: ++startTime,
+                args: {data: {}},
+                cat: 'blink.user_timing',
+                name: markName,
+                ph: 'R',
+              });
+            });
+
+            const data = await preprocessData([
+              cpuProfilerSample,
+              ...createBoilerplateEntries(),
+              ...testMarks,
+            ]);
+
+            data.schedulingEvents.forEach(event => {
+              expect(event.warning).toBeNull();
+            });
           });
 
-          expect(Scheduler).toHaveYielded([
-            'Component rendered with value 0 and deferredValue 0',
-            'Component rendered with value 1 and deferredValue 0',
-            'Component rendered with value 1 and deferredValue 1',
-            'Long render',
-          ]);
-
-          const testMarks = [];
-          clearedMarks.forEach(markName => {
-            if (markName === '--component-render-start-Component') {
-              // Fake a long running render
-              startTime += 20000;
+          it('should not warn about deferred value updates scheduled during commit phase', async () => {
+            function Component() {
+              const [value, setValue] = React.useState(0);
+              const deferredValue = React.useDeferredValue(value);
+
+              Scheduler.unstable_yieldValue(
+                `Component rendered with value ${value} and deferredValue ${deferredValue}`,
+              );
+
+              // Fake a long render
+              if (deferredValue !== 0) {
+                Scheduler.unstable_yieldValue('Long render');
+                startTime += 20000;
+              }
+
+              React.useLayoutEffect(() => {
+                setValue(1);
+              }, []);
+
+              return value + deferredValue;
             }
 
-            testMarks.push({
-              pid: ++pid,
-              tid: ++tid,
-              ts: ++startTime,
-              args: {data: {}},
-              cat: 'blink.user_timing',
-              name: markName,
-              ph: 'R',
+            const cpuProfilerSample = creactCpuProfilerSample();
+
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() => {
+              root.render(<Component />);
             });
-          });
 
-          const data = await preprocessData([
-            cpuProfilerSample,
-            ...createBoilerplateEntries(),
-            ...testMarks,
-          ]);
+            expect(Scheduler).toHaveYielded([
+              'Component rendered with value 0 and deferredValue 0',
+              'Component rendered with value 1 and deferredValue 0',
+              'Component rendered with value 1 and deferredValue 1',
+              'Long render',
+            ]);
+
+            const testMarks = [];
+            clearedMarks.forEach(markName => {
+              if (markName === '--component-render-start-Component') {
+                // Fake a long running render
+                startTime += 20000;
+              }
 
-          data.schedulingEvents.forEach(event => {
-            expect(event.warning).toBeNull();
+              testMarks.push({
+                pid: ++pid,
+                tid: ++tid,
+                ts: ++startTime,
+                args: {data: {}},
+                cat: 'blink.user_timing',
+                name: markName,
+                ph: 'R',
+              });
+            });
+
+            const data = await preprocessData([
+              cpuProfilerSample,
+              ...createBoilerplateEntries(),
+              ...testMarks,
+            ]);
+
+            data.schedulingEvents.forEach(event => {
+              expect(event.warning).toBeNull();
+            });
           });
         });
-      });
 
-      describe('errors thrown while rendering', () => {
-        it('shoult parse Errors thrown during render', async () => {
-          spyOn(console, 'error');
+        describe('errors thrown while rendering', () => {
+          it('shoult parse Errors thrown during render', async () => {
+            spyOn(console, 'error');
 
-          class ErrorBoundary extends React.Component {
-            state = {error: null};
-            componentDidCatch(error) {
-              this.setState({error});
-            }
-            render() {
-              if (this.state.error) {
-                return null;
+            class ErrorBoundary extends React.Component {
+              state = {error: null};
+              componentDidCatch(error) {
+                this.setState({error});
+              }
+              render() {
+                if (this.state.error) {
+                  return null;
+                }
+                return this.props.children;
               }
-              return this.props.children;
             }
-          }
 
-          function ExampleThatThrows() {
-            throw Error('Expected error');
-          }
+            function ExampleThatThrows() {
+              throw Error('Expected error');
+            }
+
+            const testMarks = [creactCpuProfilerSample()];
 
-          const testMarks = [creactCpuProfilerSample()];
-
-          // Mount and commit the app
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() =>
-            root.render(
-              <ErrorBoundary>
-                <ExampleThatThrows />
-              </ErrorBoundary>,
-            ),
-          );
-
-          testMarks.push(...createUserTimingData(clearedMarks));
-
-          const data = await preprocessData(testMarks);
-          expect(data.thrownErrors).toHaveLength(2);
-          expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
-            '"Expected error"',
-          );
+            // Mount and commit the app
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() =>
+              root.render(
+                <ErrorBoundary>
+                  <ExampleThatThrows />
+                </ErrorBoundary>,
+              ),
+            );
+
+            testMarks.push(...createUserTimingData(clearedMarks));
+
+            const data = await preprocessData(testMarks);
+            expect(data.thrownErrors).toHaveLength(2);
+            expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
+              '"Expected error"',
+            );
+          });
         });
-      });
 
-      describe('suspend during an update', () => {
-        // This also tests an edge case where the a component suspends while profiling
-        // before the first commit is logged (so the lane-to-labels map will not yet exist).
-        it('should warn about suspending during an udpate', async () => {
-          let promise = null;
-          let resolvedValue = null;
-          function readValue(value) {
-            if (resolvedValue !== null) {
-              return resolvedValue;
-            } else if (promise === null) {
-              promise = Promise.resolve(true).then(() => {
-                resolvedValue = value;
-              });
+        describe('suspend during an update', () => {
+          // This also tests an edge case where the a component suspends while profiling
+          // before the first commit is logged (so the lane-to-labels map will not yet exist).
+          it('should warn about suspending during an udpate', async () => {
+            let promise = null;
+            let resolvedValue = null;
+            function readValue(value) {
+              if (resolvedValue !== null) {
+                return resolvedValue;
+              } else if (promise === null) {
+                promise = Promise.resolve(true).then(() => {
+                  resolvedValue = value;
+                });
+              }
+              throw promise;
             }
-            throw promise;
-          }
 
-          function Component({shouldSuspend}) {
-            Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
-            if (shouldSuspend) {
-              readValue(123);
+            function Component({shouldSuspend}) {
+              Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
+              if (shouldSuspend) {
+                readValue(123);
+              }
+              return null;
             }
-            return null;
-          }
 
-          // Mount and commit the app
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() =>
-            root.render(
-              <React.Suspense fallback="Loading...">
-                <Component shouldSuspend={false} />
-              </React.Suspense>,
-            ),
-          );
-
-          clearPendingMarks();
-
-          const testMarks = [creactCpuProfilerSample()];
-
-          // Start profiling and suspend during a render.
-          utils.act(() =>
-            root.render(
-              <React.Suspense fallback="Loading...">
-                <Component shouldSuspend={true} />
-              </React.Suspense>,
-            ),
-          );
-
-          testMarks.push(...createUserTimingData(clearedMarks));
-
-          let data;
-          await utils.actAsync(async () => {
-            data = await preprocessData(testMarks);
+            // Mount and commit the app
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() =>
+              root.render(
+                <React.Suspense fallback="Loading...">
+                  <Component shouldSuspend={false} />
+                </React.Suspense>,
+              ),
+            );
+
+            const testMarks = [creactCpuProfilerSample()];
+
+            // Start profiling and suspend during a render.
+            utils.act(() =>
+              root.render(
+                <React.Suspense fallback="Loading...">
+                  <Component shouldSuspend={true} />
+                </React.Suspense>,
+              ),
+            );
+
+            testMarks.push(...createUserTimingData(clearedMarks));
+
+            let data;
+            await utils.actAsync(async () => {
+              data = await preprocessData(testMarks);
+            });
+            expect(data.suspenseEvents).toHaveLength(1);
+            expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
+              `"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
+            );
           });
-          expect(data.suspenseEvents).toHaveLength(1);
-          expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
-            `"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
-          );
-        });
 
-        it('should not warn about suspending during an transition', async () => {
-          let promise = null;
-          let resolvedValue = null;
-          function readValue(value) {
-            if (resolvedValue !== null) {
-              return resolvedValue;
-            } else if (promise === null) {
-              promise = Promise.resolve(true).then(() => {
-                resolvedValue = value;
-              });
+          it('should not warn about suspending during an transition', async () => {
+            let promise = null;
+            let resolvedValue = null;
+            function readValue(value) {
+              if (resolvedValue !== null) {
+                return resolvedValue;
+              } else if (promise === null) {
+                promise = Promise.resolve(true).then(() => {
+                  resolvedValue = value;
+                });
+              }
+              throw promise;
             }
-            throw promise;
-          }
 
-          function Component({shouldSuspend}) {
-            Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
-            if (shouldSuspend) {
-              readValue(123);
+            function Component({shouldSuspend}) {
+              Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
+              if (shouldSuspend) {
+                readValue(123);
+              }
+              return null;
             }
-            return null;
-          }
 
-          // Mount and commit the app
-          const root = ReactDOM.createRoot(document.createElement('div'));
-          utils.act(() =>
-            root.render(
-              <React.Suspense fallback="Loading...">
-                <Component shouldSuspend={false} />
-              </React.Suspense>,
-            ),
-          );
-
-          const testMarks = [creactCpuProfilerSample()];
-
-          // Start profiling and suspend during a render.
-          await utils.actAsync(async () =>
-            React.startTransition(() =>
+            // Mount and commit the app
+            const root = ReactDOM.createRoot(document.createElement('div'));
+            utils.act(() =>
               root.render(
                 <React.Suspense fallback="Loading...">
-                  <Component shouldSuspend={true} />
+                  <Component shouldSuspend={false} />
                 </React.Suspense>,
               ),
-            ),
-          );
+            );
 
-          testMarks.push(...createUserTimingData(clearedMarks));
+            const testMarks = [creactCpuProfilerSample()];
 
-          let data;
-          await utils.actAsync(async () => {
-            data = await preprocessData(testMarks);
+            // Start profiling and suspend during a render.
+            await utils.actAsync(async () =>
+              React.startTransition(() =>
+                root.render(
+                  <React.Suspense fallback="Loading...">
+                    <Component shouldSuspend={true} />
+                  </React.Suspense>,
+                ),
+              ),
+            );
+
+            testMarks.push(...createUserTimingData(clearedMarks));
+
+            let data;
+            await utils.actAsync(async () => {
+              data = await preprocessData(testMarks);
+            });
+            expect(data.suspenseEvents).toHaveLength(1);
+            expect(data.suspenseEvents[0].warning).toBe(null);
           });
-          expect(data.suspenseEvents).toHaveLength(1);
-          expect(data.suspenseEvents[0].warning).toBe(null);
         });
       });
-    });
 
-    // TODO: Add test for snapshot base64 parsing
+      // TODO: Add test for snapshot base64 parsing
 
-    // TODO: Add test for flamechart parsing
+      // TODO: Add test for flamechart parsing
+    });
   });
 });
diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index 4cf491363262c..ee7c65a156827 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -915,17 +915,17 @@ describe('Store', () => {
     const containerA = document.createElement('div');
     const containerB = document.createElement('div');
 
-    expect(store.supportsProfiling).toBe(false);
+    expect(store.rootSupportsBasicProfiling).toBe(false);
 
     act(() => legacyRender(<Component />, containerA));
-    expect(store.supportsProfiling).toBe(true);
+    expect(store.rootSupportsBasicProfiling).toBe(true);
 
     act(() => legacyRender(<Component />, containerB));
     act(() => ReactDOM.unmountComponentAtNode(containerA));
-    expect(store.supportsProfiling).toBe(true);
+    expect(store.rootSupportsBasicProfiling).toBe(true);
 
     act(() => ReactDOM.unmountComponentAtNode(containerB));
-    expect(store.supportsProfiling).toBe(false);
+    expect(store.rootSupportsBasicProfiling).toBe(false);
   });
 
   it('should properly serialize non-string key values', () => {
diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js
index 0a1896ee322c4..f24b5bac85bb6 100644
--- a/packages/react-devtools-shared/src/backend/legacy/renderer.js
+++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js
@@ -387,7 +387,7 @@ export function attach(
       pushOperation(id);
       pushOperation(ElementTypeRoot);
       pushOperation(0); // StrictMode compliant?
-      pushOperation(0); // Profiling supported?
+      pushOperation(0); // Profiling flag
       pushOperation(0); // StrictMode supported?
       pushOperation(hasOwnerMetadata ? 1 : 0);
     } else {
diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js
index 2280d6156e683..f09137f5b8ef9 100644
--- a/packages/react-devtools-shared/src/backend/profilingHooks.js
+++ b/packages/react-devtools-shared/src/backend/profilingHooks.js
@@ -63,21 +63,26 @@ export function setPerformanceMock_ONLY_FOR_TESTING(
   supportsUserTimingV3 = performanceMock !== null;
 }
 
-function markAndClear(markName) {
-  // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
-  ((performanceTarget: any): Performance).mark(markName);
-  ((performanceTarget: any): Performance).clearMarks(markName);
-}
+export type ToggleProfilingStatus = (value: boolean) => void;
+
+type Response = {|
+  profilingHooks: DevToolsProfilingHooks,
+  toggleProfilingStatus: ToggleProfilingStatus,
+|};
 
 export function createProfilingHooks({
   getDisplayNameForFiber,
+  getIsProfiling,
   getLaneLabelMap,
   reactVersion,
 }: {|
   getDisplayNameForFiber: (fiber: Fiber) => string | null,
+  getIsProfiling: () => boolean,
   getLaneLabelMap?: () => Map<Lane, string> | null,
   reactVersion: string,
-|}): DevToolsProfilingHooks {
+|}): Response {
+  let isProfiling: boolean = false;
+
   function markMetadata() {
     markAndClear(`--react-version-${reactVersion}`);
     markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
@@ -115,19 +120,25 @@ export function createProfilingHooks({
     }
   }
 
+  function markAndClear(markName) {
+    // Only record User Timing marks if DevTools is profiling.
+    if (!isProfiling) {
+      return;
+    }
+
+    // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
+    ((performanceTarget: any): Performance).mark(markName);
+    ((performanceTarget: any): Performance).clearMarks(markName);
+  }
+
   function markCommitStarted(lanes: Lanes): void {
     if (supportsUserTimingV3) {
       markAndClear(`--commit-start-${lanes}`);
 
-      // Certain types of metadata should be logged infrequently.
-      // Normally we would log this during module init,
-      // but there's no guarantee a user is profiling at that time.
-      // Commits happen infrequently (less than renders or state updates)
-      // so we log this extra information along with a commit.
-      // It will likely be logged more than once but that's okay.
-      //
-      // TODO (timeline) Only log this once, when profiling starts.
-      // For the first phase– refactoring– we'll match the previous behavior.
+      // Some metadata only needs to be logged once per session,
+      // but if profiling information is being recorded via the Performance tab,
+      // DevTools has no way of knowing when the recording starts.
+      // Because of that, we log thie type of data periodically (once per commit).
       markMetadata();
     }
   }
@@ -337,30 +348,47 @@ export function createProfilingHooks({
     }
   }
 
+  function toggleProfilingStatus(value: boolean) {
+    if (isProfiling !== value) {
+      isProfiling = value;
+
+      if (supportsUserTimingV3) {
+        if (isProfiling) {
+          // TODO (timeline)
+          // Some metadata only needs to be logged once per session.
+          // Store it at the start of the session.
+        }
+      }
+    }
+  }
+
   return {
-    markCommitStarted,
-    markCommitStopped,
-    markComponentRenderStarted,
-    markComponentRenderStopped,
-    markComponentPassiveEffectMountStarted,
-    markComponentPassiveEffectMountStopped,
-    markComponentPassiveEffectUnmountStarted,
-    markComponentPassiveEffectUnmountStopped,
-    markComponentLayoutEffectMountStarted,
-    markComponentLayoutEffectMountStopped,
-    markComponentLayoutEffectUnmountStarted,
-    markComponentLayoutEffectUnmountStopped,
-    markComponentErrored,
-    markComponentSuspended,
-    markLayoutEffectsStarted,
-    markLayoutEffectsStopped,
-    markPassiveEffectsStarted,
-    markPassiveEffectsStopped,
-    markRenderStarted,
-    markRenderYielded,
-    markRenderStopped,
-    markRenderScheduled,
-    markForceUpdateScheduled,
-    markStateUpdateScheduled,
+    profilingHooks: {
+      markCommitStarted,
+      markCommitStopped,
+      markComponentRenderStarted,
+      markComponentRenderStopped,
+      markComponentPassiveEffectMountStarted,
+      markComponentPassiveEffectMountStopped,
+      markComponentPassiveEffectUnmountStarted,
+      markComponentPassiveEffectUnmountStopped,
+      markComponentLayoutEffectMountStarted,
+      markComponentLayoutEffectMountStopped,
+      markComponentLayoutEffectUnmountStarted,
+      markComponentLayoutEffectUnmountStopped,
+      markComponentErrored,
+      markComponentSuspended,
+      markLayoutEffectsStarted,
+      markLayoutEffectsStopped,
+      markPassiveEffectsStarted,
+      markPassiveEffectsStopped,
+      markRenderStarted,
+      markRenderYielded,
+      markRenderStopped,
+      markRenderScheduled,
+      markForceUpdateScheduled,
+      markStateUpdateScheduled,
+    },
+    toggleProfilingStatus,
   };
 }
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index 9a60fe42376a2..9f4d19a67285b 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -47,6 +47,8 @@ import {
 } from './utils';
 import {
   __DEBUG__,
+  PROFILING_FLAG_BASIC_SUPPORT,
+  PROFILING_FLAG_TIMELINE_SUPPORT,
   SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
   SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
   TREE_OPERATION_ADD,
@@ -94,6 +96,7 @@ import hasOwnProperty from 'shared/hasOwnProperty';
 import {getStyleXData} from './StyleX/utils';
 import {createProfilingHooks} from './profilingHooks';
 
+import type {ToggleProfilingStatus} from './profilingHooks';
 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
 import type {
   ChangeDescription,
@@ -627,14 +630,20 @@ export function attach(
     };
   }
 
+  let toggleProfilingStatus: null | ToggleProfilingStatus = null;
   if (typeof injectProfilingHooks === 'function') {
-    injectProfilingHooks(
-      createProfilingHooks({
-        getDisplayNameForFiber,
-        getLaneLabelMap,
-        reactVersion: version,
-      }),
-    );
+    const response = createProfilingHooks({
+      getDisplayNameForFiber,
+      getIsProfiling: () => isProfiling,
+      getLaneLabelMap,
+      reactVersion: version,
+    });
+
+    // Pass the Profiling hooks to the reconciler for it to call during render.
+    injectProfilingHooks(response.profilingHooks);
+
+    // Hang onto this toggle so we can notify the external methods of profiling status changes.
+    toggleProfilingStatus = response.toggleProfilingStatus;
   }
 
   // Tracks Fibers with recently changed number of error/warning messages.
@@ -1910,12 +1919,22 @@ export function attach(
     const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
     const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
 
+    // Adding a new field here would require a bridge protocol version bump (a backwads breaking change).
+    // Instead let's re-purpose a pre-existing field to carry more information.
+    let profilingFlags = 0;
+    if (isProfilingSupported) {
+      profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;
+      if (typeof injectProfilingHooks === 'function') {
+        profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT;
+      }
+    }
+
     if (isRoot) {
       pushOperation(TREE_OPERATION_ADD);
       pushOperation(id);
       pushOperation(ElementTypeRoot);
       pushOperation((fiber.mode & StrictModeBits) !== 0 ? 1 : 0);
-      pushOperation(isProfilingSupported ? 1 : 0);
+      pushOperation(profilingFlags);
       pushOperation(StrictModeBits !== 0 ? 1 : 0);
       pushOperation(hasOwnerMetadata ? 1 : 0);
 
@@ -3999,11 +4018,19 @@ export function attach(
     isProfiling = true;
     profilingStartTime = getCurrentTime();
     rootToCommitProfilingMetadataMap = new Map();
+
+    if (toggleProfilingStatus !== null) {
+      toggleProfilingStatus(true);
+    }
   }
 
   function stopProfiling() {
     isProfiling = false;
     recordChangeDescriptions = false;
+
+    if (toggleProfilingStatus !== null) {
+      toggleProfilingStatus(false);
+    }
   }
 
   // Automatically start profiling so that we don't miss timing info from initial "mount".
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 99a86464236f2..befab73daa976 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -195,6 +195,7 @@ export type ProfilingDataForRootBackend = {|
 export type ProfilingDataBackend = {|
   dataForRoots: Array<ProfilingDataForRootBackend>,
   rendererID: number,
+  // TODO (timeline) Add (optional) Timeline data.
 |};
 
 export type PathFrame = {|
diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js
index 6480222e1e414..d7fafc082ce24 100644
--- a/packages/react-devtools-shared/src/constants.js
+++ b/packages/react-devtools-shared/src/constants.js
@@ -25,6 +25,9 @@ export const TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS = 5;
 export const TREE_OPERATION_REMOVE_ROOT = 6;
 export const TREE_OPERATION_SET_SUBTREE_MODE = 7;
 
+export const PROFILING_FLAG_BASIC_SUPPORT = 0b01;
+export const PROFILING_FLAG_TIMELINE_SUPPORT = 0b10;
+
 export const LOCAL_STORAGE_DEFAULT_TAB_KEY = 'React::DevTools::defaultTab';
 
 export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index b68ea3d26f92e..a2fda1825fcaa 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -10,6 +10,8 @@
 import EventEmitter from '../events';
 import {inspect} from 'util';
 import {
+  PROFILING_FLAG_BASIC_SUPPORT,
+  PROFILING_FLAG_TIMELINE_SUPPORT,
   TREE_OPERATION_ADD,
   TREE_OPERATION_REMOVE,
   TREE_OPERATION_REMOVE_ROOT,
@@ -72,9 +74,10 @@ type Config = {|
 |};
 
 export type Capabilities = {|
+  supportsBasicProfiling: boolean,
   hasOwnerMetadata: boolean,
-  supportsProfiling: boolean,
   supportsStrictMode: boolean,
+  supportsTimeline: boolean,
 |};
 
 /**
@@ -88,8 +91,9 @@ export default class Store extends EventEmitter<{|
   mutated: [[Array<number>, Map<number, number>]],
   recordChangeDescriptions: [],
   roots: [],
+  rootSupportsBasicProfiling: [],
+  rootSupportsTimelineProfiling: [],
   supportsNativeStyleEditor: [],
-  supportsProfiling: [],
   supportsReloadAndProfile: [],
   unsupportedBridgeProtocolDetected: [],
   unsupportedRendererVersionDetected: [],
@@ -161,13 +165,16 @@ export default class Store extends EventEmitter<{|
   _rootIDToRendererID: Map<number, number> = new Map();
 
   // These options may be initially set by a confiugraiton option when constructing the Store.
-  // In the case of "supportsProfiling", the option may be updated based on the injected renderers.
   _supportsNativeInspection: boolean = true;
   _supportsProfiling: boolean = false;
   _supportsReloadAndProfile: boolean = false;
   _supportsTimeline: boolean = false;
   _supportsTraceUpdates: boolean = false;
 
+  // These options default to false but may be updated as roots are added and removed.
+  _rootSupportsBasicProfiling: boolean = false;
+  _rootSupportsTimelineProfiling: boolean = false;
+
   _unsupportedBridgeProtocol: BridgeProtocol | null = null;
   _unsupportedRendererVersionDetected: boolean = false;
 
@@ -402,6 +409,16 @@ export default class Store extends EventEmitter<{|
     return this._roots;
   }
 
+  // At least one of the currently mounted roots support the Legacy profiler.
+  get rootSupportsBasicProfiling(): boolean {
+    return this._rootSupportsBasicProfiling;
+  }
+
+  // At least one of the currently mounted roots support the Timeline profiler.
+  get rootSupportsTimelineProfiling(): boolean {
+    return this._rootSupportsTimelineProfiling;
+  }
+
   get supportsNativeInspection(): boolean {
     return this._supportsNativeInspection;
   }
@@ -410,6 +427,8 @@ export default class Store extends EventEmitter<{|
     return this._isNativeStyleEditorSupported;
   }
 
+  // This build of DevTools supports the legacy profiler.
+  // This is a static flag, controled by the Store config.
   get supportsProfiling(): boolean {
     return this._supportsProfiling;
   }
@@ -425,6 +444,8 @@ export default class Store extends EventEmitter<{|
     );
   }
 
+  // This build of DevTools supports the Timeline profiler.
+  // This is a static flag, controled by the Store config.
   get supportsTimeline(): boolean {
     return this._supportsTimeline;
   }
@@ -903,7 +924,10 @@ export default class Store extends EventEmitter<{|
             const isStrictModeCompliant = operations[i] > 0;
             i++;
 
-            const supportsProfiling = operations[i] > 0;
+            const supportsBasicProfiling =
+              (operations[i] & PROFILING_FLAG_BASIC_SUPPORT) !== 0;
+            const supportsTimeline =
+              (operations[i] & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0;
             i++;
 
             const supportsStrictMode = operations[i] > 0;
@@ -915,9 +939,10 @@ export default class Store extends EventEmitter<{|
             this._roots = this._roots.concat(id);
             this._rootIDToRendererID.set(id, rendererID);
             this._rootIDToCapabilities.set(id, {
+              supportsBasicProfiling,
               hasOwnerMetadata,
-              supportsProfiling,
               supportsStrictMode,
+              supportsTimeline,
             });
 
             // Not all roots support StrictMode;
@@ -1224,25 +1249,38 @@ export default class Store extends EventEmitter<{|
     }
 
     if (haveRootsChanged) {
-      const prevSupportsProfiling = this._supportsProfiling;
+      const prevRootSupportsProfiling = this._rootSupportsBasicProfiling;
+      const prevRootSupportsTimelineProfiling = this
+        ._rootSupportsTimelineProfiling;
 
       this._hasOwnerMetadata = false;
-      this._supportsProfiling = false;
+      this._rootSupportsBasicProfiling = false;
+      this._rootSupportsTimelineProfiling = false;
       this._rootIDToCapabilities.forEach(
-        ({hasOwnerMetadata, supportsProfiling}) => {
+        ({supportsBasicProfiling, hasOwnerMetadata, supportsTimeline}) => {
+          if (supportsBasicProfiling) {
+            this._rootSupportsBasicProfiling = true;
+          }
           if (hasOwnerMetadata) {
             this._hasOwnerMetadata = true;
           }
-          if (supportsProfiling) {
-            this._supportsProfiling = true;
+          if (supportsTimeline) {
+            this._rootSupportsTimelineProfiling = true;
           }
         },
       );
 
       this.emit('roots');
 
-      if (this._supportsProfiling !== prevSupportsProfiling) {
-        this.emit('supportsProfiling');
+      if (this._rootSupportsBasicProfiling !== prevRootSupportsProfiling) {
+        this.emit('rootSupportsBasicProfiling');
+      }
+
+      if (
+        this._rootSupportsTimelineProfiling !==
+        prevRootSupportsTimelineProfiling
+      ) {
+        this.emit('rootSupportsTimelineProfiling');
       }
     }
 
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
index 8fbebfeba86dc..53f918d206679 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
@@ -17,30 +17,25 @@ import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
 
 export default function ClearProfilingDataButton() {
   const store = useContext(StoreContext);
-  const {didRecordCommits, isProfiling, selectedTabID} = useContext(
-    ProfilerContext,
-  );
+  const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
   const {file, setFile} = useContext(TimelineContext);
   const {profilerStore} = store;
 
-  let doesHaveData = false;
-  if (selectedTabID === 'timeline') {
-    doesHaveData = file !== null;
-  } else {
-    doesHaveData = didRecordCommits;
-  }
+  const doesHaveLegacyData = didRecordCommits;
+  const doesHaveTimelineData = file !== null;
 
   const clear = () => {
-    if (selectedTabID === 'timeline') {
-      setFile(null);
-    } else {
+    if (doesHaveLegacyData) {
       profilerStore.clear();
     }
+    if (doesHaveTimelineData) {
+      setFile(null);
+    }
   };
 
   return (
     <Button
-      disabled={isProfiling || !doesHaveData}
+      disabled={isProfiling || !(doesHaveLegacyData || doesHaveTimelineData)}
       onClick={clear}
       title="Clear profiling data">
       <ButtonIcon type="clear" />
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
index 286048a172bf5..537f226f2d69b 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
@@ -194,7 +194,7 @@ function updateTree(
 
         if (type === ElementTypeRoot) {
           i++; // isStrictModeCompliant
-          i++; // supportsProfiling flag
+          i++; // Profiling flag
           i++; // supportsStrictMode flag
           i++; // hasOwnerMetadata flag
 
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js b/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
new file mode 100644
index 0000000000000..231de1c1d5a1a
--- /dev/null
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import RecordToggle from './RecordToggle';
+
+import styles from './Profiler.css';
+
+export default function NoProfilingData() {
+  return (
+    <div className={styles.Column}>
+      <div className={styles.Header}>No profiling data has been recorded.</div>
+      <div className={styles.Row}>
+        Click the record button <RecordToggle /> to start recording.
+      </div>
+      <div className={`${styles.Row} ${styles.LearnMoreRow}`}>
+        Click{' '}
+        <a
+          className={styles.LearnMoreLink}
+          href="https://fb.me/react-devtools-profiling"
+          rel="noopener noreferrer"
+          target="_blank">
+          here
+        </a>{' '}
+        to learn more about profiling.
+      </div>
+    </div>
+  );
+}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
index 45f3352cec6a9..a317e3fcf4294 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
@@ -61,6 +61,12 @@
   justify-content: center;
 }
 
+.LearnMoreRow {
+  margin-top: 1rem;
+  color: var(--color-dim);
+  font-size: var(--font-size-sans-small);
+}
+
 .Header {
   font-size: var(--font-size-sans-large);
   margin-bottom: 0.5rem;
@@ -121,3 +127,9 @@
   display: flex;
   align-items: center;
 }
+
+.LearnMoreLink {
+  color: var(--color-link);
+  margin-left: 0.25rem;
+  margin-right: 0.25rem;
+}
\ No newline at end of file
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
index 8f5de6237e865..3ac740324609a 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
@@ -22,6 +22,8 @@ import ReloadAndProfileButton from './ReloadAndProfileButton';
 import ProfilingImportExportButtons from './ProfilingImportExportButtons';
 import SnapshotSelector from './SnapshotSelector';
 import SidebarCommitInfo from './SidebarCommitInfo';
+import NoProfilingData from './NoProfilingData';
+import ProfilingNotSupported from './ProfilingNotSupported';
 import SidebarSelectedFiberInfo from './SidebarSelectedFiberInfo';
 import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
 import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
@@ -104,9 +106,7 @@ function Profiler(_: {||}) {
       <div className={styles.Profiler}>
         <div className={styles.LeftColumn}>
           <div className={styles.Toolbar}>
-            <RecordToggle
-              disabled={!supportsProfiling || selectedTabID === 'timeline'}
-            />
+            <RecordToggle disabled={!supportsProfiling} />
             <ReloadAndProfileButton
               disabled={selectedTabID === 'timeline' || !supportsProfiling}
             />
@@ -175,37 +175,6 @@ const tabsWithTimeline = [
     title: 'Timeline',
   },
 ];
-
-const NoProfilingData = () => (
-  <div className={styles.Column}>
-    <div className={styles.Header}>No profiling data has been recorded.</div>
-    <div className={styles.Row}>
-      Click the record button <RecordToggle /> to start recording.
-    </div>
-  </div>
-);
-
-const ProfilingNotSupported = () => (
-  <div className={styles.Column}>
-    <div className={styles.Header}>Profiling not supported.</div>
-    <p className={styles.Paragraph}>
-      Profiling support requires either a development or production-profiling
-      build of React v16.5+.
-    </p>
-    <p className={styles.Paragraph}>
-      Learn more at{' '}
-      <a
-        className={styles.Link}
-        href="https://reactjs.org/link/profiling"
-        rel="noopener noreferrer"
-        target="_blank">
-        reactjs.org/link/profiling
-      </a>
-      .
-    </p>
-  </div>
-);
-
 const ProcessingData = () => (
   <div className={styles.Column}>
     <div className={styles.Header}>Processing data...</div>
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
index 6e486091e1419..8b08e73328432 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
@@ -96,18 +96,18 @@ function ProfilerContextController({children}: Props) {
         isProcessingData: profilerStore.isProcessingData,
         isProfiling: profilerStore.isProfiling,
         profilingData: profilerStore.profilingData,
-        supportsProfiling: store.supportsProfiling,
+        supportsProfiling: store.rootSupportsBasicProfiling,
       }),
       subscribe: (callback: Function) => {
         profilerStore.addListener('profilingData', callback);
         profilerStore.addListener('isProcessingData', callback);
         profilerStore.addListener('isProfiling', callback);
-        store.addListener('supportsProfiling', callback);
+        store.addListener('rootSupportsBasicProfiling', callback);
         return () => {
           profilerStore.removeListener('profilingData', callback);
           profilerStore.removeListener('isProcessingData', callback);
           profilerStore.removeListener('isProfiling', callback);
-          store.removeListener('supportsProfiling', callback);
+          store.removeListener('rootSupportsBasicProfiling', callback);
         };
       },
     }),
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js
new file mode 100644
index 0000000000000..4dcbe64cef069
--- /dev/null
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+
+import styles from './Profiler.css';
+
+export default function ProfilingNotSupported() {
+  return (
+    <div className={styles.Column}>
+      <div className={styles.Header}>Profiling not supported.</div>
+      <p className={styles.Paragraph}>
+        Profiling support requires either a development or profiling build of
+        React v16.5+.
+      </p>
+      <p className={styles.Paragraph}>
+        Learn more at{' '}
+        <a
+          className={styles.Link}
+          href="https://fb.me/react-devtools-profiling"
+          rel="noopener noreferrer"
+          target="_blank">
+          reactjs.org/link/profiling
+        </a>
+        .
+      </p>
+    </div>
+  );
+}
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
index c3889d47c2b32..2ab7c95170e14 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js
@@ -106,6 +106,7 @@ export type ProfilingDataFrontend = {|
   // Profiling data per root.
   dataForRoots: Map<number, ProfilingDataForRootFrontend>,
   imported: boolean,
+  // TODO (timeline) Add (optional) Timeline data.
 |};
 
 export type CommitDataExport = {|
@@ -136,4 +137,5 @@ export type ProfilingDataForRootExport = {|
 export type ProfilingDataExport = {|
   version: 5,
   dataForRoots: Array<ProfilingDataForRootExport>,
+  // TODO (timeline) Add (optional) Timeline data.
 |};
diff --git a/packages/react-devtools-timeline/src/Timeline.js b/packages/react-devtools-timeline/src/Timeline.js
index 290830840356e..20feb8eb4fc65 100644
--- a/packages/react-devtools-timeline/src/Timeline.js
+++ b/packages/react-devtools-timeline/src/Timeline.js
@@ -9,7 +9,6 @@
 
 import type {ViewState} from './types';
 
-import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
 import * as React from 'react';
 import {
   Suspense,
@@ -20,18 +19,22 @@ import {
   useState,
 } from 'react';
 import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
+import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
 import {updateColorsToMatchTheme} from './content-views/constants';
 import {TimelineContext} from './TimelineContext';
 import ImportButton from './ImportButton';
 import CanvasPage from './CanvasPage';
 import {importFile} from './timelineCache';
 import TimelineSearchInput from './TimelineSearchInput';
+import TimelineNotSupported from './TimelineNotSupported';
 import {TimelineSearchContextController} from './TimelineSearchContext';
 
 import styles from './Timeline.css';
 
 export function Timeline(_: {||}) {
-  const {file, setFile, viewState} = useContext(TimelineContext);
+  const {file, isTimelineSupported, setFile, viewState} = useContext(
+    TimelineContext,
+  );
 
   const ref = useRef(null);
 
@@ -71,55 +74,15 @@ export function Timeline(_: {||}) {
             viewState={viewState}
           />
         </Suspense>
+      ) : isTimelineSupported ? (
+        <NoProfilingData />
       ) : (
-        <Welcome onFileSelect={setFile} />
+        <TimelineNotSupported />
       )}
     </div>
   );
 }
 
-const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
-  <ol className={styles.WelcomeInstructionsList}>
-    {isInternalFacebookBuild && (
-      <li className={styles.WelcomeInstructionsListItem}>
-        Enable the
-        <a
-          className={styles.WelcomeInstructionsListItemLink}
-          href="https://fburl.com/react-devtools-scheduling-profiler-gk"
-          rel="noopener noreferrer"
-          target="_blank">
-          <code>react_enable_scheduling_profiler</code> GK
-        </a>
-        .
-      </li>
-    )}
-    <li className={styles.WelcomeInstructionsListItem}>
-      Open a website that's built with the
-      <a
-        className={styles.WelcomeInstructionsListItemLink}
-        href="https://reactjs.org/link/profiling"
-        rel="noopener noreferrer"
-        target="_blank">
-        profiling build of ReactDOM
-      </a>
-      (version 18 or newer).
-    </li>
-    <li className={styles.WelcomeInstructionsListItem}>
-      Open the "Performance" tab in Chrome and record some performance data.
-    </li>
-    <li className={styles.WelcomeInstructionsListItem}>
-      Click the "Save profile..." button in Chrome to export the data.
-    </li>
-    <li className={styles.WelcomeInstructionsListItem}>
-      Import the data into the profiler:
-      <br />
-      <ImportButton onFileSelect={onFileSelect}>
-        <span className={styles.ImportButtonLabel}>Import</span>
-      </ImportButton>
-    </li>
-  </ol>
-);
-
 const ProcessingData = () => (
   <div className={styles.EmptyStateContainer}>
     <div className={styles.Header}>Processing data...</div>
diff --git a/packages/react-devtools-timeline/src/TimelineContext.js b/packages/react-devtools-timeline/src/TimelineContext.js
index cbf57a275c0c5..fbf52b5802479 100644
--- a/packages/react-devtools-timeline/src/TimelineContext.js
+++ b/packages/react-devtools-timeline/src/TimelineContext.js
@@ -8,7 +8,15 @@
  */
 
 import * as React from 'react';
-import {createContext, useMemo, useRef, useState} from 'react';
+import {
+  createContext,
+  useContext,
+  useMemo,
+  useRef,
+  useState,
+  useSyncExternalStore,
+} from 'react';
+import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
 
 import type {
   HorizontalScrollStateChangeCallback,
@@ -19,6 +27,7 @@ import type {RefObject} from 'shared/ReactTypes';
 
 export type Context = {|
   file: File | null,
+  isTimelineSupported: boolean,
   searchInputContainerRef: RefObject,
   setFile: (file: File | null) => void,
   viewState: ViewState,
@@ -35,6 +44,20 @@ function TimelineContextController({children}: Props) {
   const searchInputContainerRef = useRef(null);
   const [file, setFile] = useState<string | null>(null);
 
+  const store = useContext(StoreContext);
+
+  const isTimelineSupported = useSyncExternalStore<boolean>(
+    function subscribe(callback) {
+      store.addListener('rootSupportsTimelineProfiling', callback);
+      return function unsubscribe() {
+        store.removeListener('rootSupportsTimelineProfiling', callback);
+      };
+    },
+    function getState() {
+      return store.rootSupportsTimelineProfiling;
+    },
+  );
+
   // Recreate view state any time new profiling data is imported.
   const viewState = useMemo<ViewState>(() => {
     const horizontalScrollStateChangeCallbacks: Set<HorizontalScrollStateChangeCallback> = new Set();
@@ -85,11 +108,12 @@ function TimelineContextController({children}: Props) {
   const value = useMemo(
     () => ({
       file,
+      isTimelineSupported,
       searchInputContainerRef,
       setFile,
       viewState,
     }),
-    [file, setFile, viewState],
+    [file, isTimelineSupported, setFile, viewState],
   );
 
   return (
diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.css b/packages/react-devtools-timeline/src/TimelineNotSupported.css
new file mode 100644
index 0000000000000..971723520a456
--- /dev/null
+++ b/packages/react-devtools-timeline/src/TimelineNotSupported.css
@@ -0,0 +1,38 @@
+.Column {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 0 1rem;
+}
+
+.Header {
+  font-size: var(--font-size-sans-large);
+  margin-bottom: 0.5rem;
+}
+
+.Paragraph {
+  text-align: center;
+  margin: 0;
+}
+
+.Link {
+  color: var(--color-link);
+}
+
+.LearnMoreRow {
+  margin-top: 1rem;
+  color: var(--color-dim);
+  font-size: var(--font-size-sans-small);
+}
+
+.Code {
+  color: var(--color-bridge-version-number);
+}
+
+.MetaGKRow {
+  background: var(--color-background-hover);
+  padding: 0.25rem 0.5rem;
+  border-radius: 0.25rem;
+  margin-top: 1rem;
+}
\ No newline at end of file
diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.js b/packages/react-devtools-timeline/src/TimelineNotSupported.js
new file mode 100644
index 0000000000000..9a21df8ab8cb3
--- /dev/null
+++ b/packages/react-devtools-timeline/src/TimelineNotSupported.js
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
+
+import styles from './TimelineNotSupported.css';
+
+export default function TimelineNotSupported() {
+  return (
+    <div className={styles.Column}>
+      <div className={styles.Header}>Timeline profiling not supported.</div>
+      <p className={styles.Paragraph}>
+        <span>
+          Timeline profiler requires a development or profiling build of{' '}
+          <code className={styles.Code}>react-dom@^18</code>.
+        </span>
+      </p>
+      <div className={styles.LearnMoreRow}>
+        Click{' '}
+        <a
+          className={styles.Link}
+          href="https://fb.me/react-devtools-profiling"
+          rel="noopener noreferrer"
+          target="_blank">
+          here
+        </a>{' '}
+        to learn more about profiling.
+      </div>
+
+      {isInternalFacebookBuild && (
+        <div className={styles.MetaGKRow}>
+          <strong>Meta only</strong>: Enable the{' '}
+          <a
+            className={styles.Link}
+            href="https://fburl.com/react-devtools-scheduling-profiler-gk"
+            rel="noopener noreferrer"
+            target="_blank">
+            react_enable_scheduling_profiler GK
+          </a>
+          .
+        </div>
+      )}
+    </div>
+  );
+}