Repository: facebook/react
Pull request: [DevTools] hotkey to start/stop profiling

## Summary

The built-in browser profiler supports starting/stopping with Cmd+E. For Symmetry this adds the same hotkey for react devtools profiler.

## How did you test this change?
yarn build:\<browser name\> 
yarn run test:\<browser name\>

<img width="483" height="135" alt="Screenshot 2025-11-17 at 14 30 34" src="https://github.com/user-attachments/assets/426939aa-15da-4c21-87a4-e949e6949482" />

firefox:
https://github.com/user-attachments/assets/6f225b90-828f-4e79-a364-59d6bc942f83

edge:
https://github.com/user-attachments/assets/5b2e9242-f0e8-481b-99a2-2dd78099f3ac

chrome:
https://github.com/user-attachments/assets/790aab02-2867-4499-aec1-e32e38c763f9


Changed files:
filename: packages/react-devtools-shared/src/__tests__/profilerContext-test.js
patch: @@ -584,4 +584,75 @@ describe('ProfilerContext', () => {
     await utils.actAsync(() => context.selectFiber(childID, 'Child'));
     expect(inspectedElementID).toBe(parentID);
   });
+
+  it('should toggle profiling when the keyboard shortcut is pressed', async () => {
+    // Context providers
+    const Profiler =
+      require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
+    const {
+      TimelineContextController,
+    } = require('react-devtools-timeline/src/TimelineContext');
+    const {
+      SettingsContextController,
+    } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
+    const {
+      ModalDialogContextController,
+    } = require('react-devtools-shared/src/devtools/views/ModalDialog');
+
+    // Dom component for profiling to be enabled
+    const Component = () => null;
+    utils.act(() => render(<Component />));
+
+    const profilerContainer = document.createElement('div');
+    document.body.appendChild(profilerContainer);
+
+    // Create a root for the profiler
+    const profilerRoot = ReactDOMClient.createRoot(profilerContainer);
+
+    // Render the profiler
+    utils.act(() => {
+      profilerRoot.render(
+        <Contexts>
+          <SettingsContextController browserTheme="light">
+            <ModalDialogContextController>
+              <TimelineContextController>
+                <Profiler />
+              </TimelineContextController>
+            </ModalDialogContextController>
+          </SettingsContextController>
+        </Contexts>,
+      );
+    });
+
+    // Verify that the profiler is not profiling.
+    expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(false);
+
+    // Trigger the keyboard shortcut.
+    const ownerWindow = profilerContainer.ownerDocument.defaultView;
+    const isMac =
+      typeof navigator !== 'undefined' &&
+      navigator.platform.toUpperCase().indexOf('MAC') >= 0;
+
+    const keyEvent = new KeyboardEvent('keydown', {
+      key: 'e',
+      metaKey: isMac,
+      ctrlKey: !isMac,
+      bubbles: true,
+    });
+
+    // Dispatch keyboard event to toggle profiling on
+    // Try utils.actAsync with recursivelyFlush=false
+    await utils.actAsync(() => {
+      ownerWindow.dispatchEvent(keyEvent);
+    }, false);
+    expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(true);
+
+    // Dispatch keyboard event to toggle profiling off
+    await utils.actAsync(() => {
+      ownerWindow.dispatchEvent(keyEvent);
+    }, false);
+    expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(false);
+
+    document.body.removeChild(profilerContainer);
+  });
 });

filename: packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
patch: @@ -8,7 +8,7 @@
  */
 
 import * as React from 'react';
-import {Fragment, useContext} from 'react';
+import {Fragment, useContext, useEffect, useRef, useEffectEvent} from 'react';
 import {ModalDialog} from '../ModalDialog';
 import {ProfilerContext} from './ProfilerContext';
 import TabBar from '../TabBar';
@@ -38,6 +38,11 @@ import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
 import styles from './Profiler.css';
 
 function Profiler(_: {}) {
+  const profilerRef = useRef<HTMLDivElement | null>(null);
+  const isMac =
+    typeof navigator !== 'undefined' &&
+    navigator.platform.toUpperCase().indexOf('MAC') >= 0;
+
   const {
     didRecordCommits,
     isProcessingData,
@@ -47,6 +52,8 @@ function Profiler(_: {}) {
     selectedTabID,
     selectTab,
     supportsProfiling,
+    startProfiling,
+    stopProfiling,
   } = useContext(ProfilerContext);
 
   const {file: timelineTraceEventData, searchInputContainerRef} =
@@ -56,6 +63,32 @@ function Profiler(_: {}) {
 
   const isLegacyProfilerSelected = selectedTabID !== 'timeline';
 
+  // Cmd+E to start/stop profiler recording
+  const handleKeyDown = useEffectEvent((event: KeyboardEvent) => {
+    const correctModifier = isMac ? event.metaKey : event.ctrlKey;
+    if (correctModifier && event.key === 'e') {
+      if (isProfiling) {
+        stopProfiling();
+      } else {
+        startProfiling();
+      }
+      event.preventDefault();
+      event.stopPropagation();
+    }
+  });
+
+  useEffect(() => {
+    const div = profilerRef.current;
+    if (!div) {
+      return;
+    }
+    const ownerWindow = div.ownerDocument.defaultView;
+    ownerWindow.addEventListener('keydown', handleKeyDown);
+    return () => {
+      ownerWindow.removeEventListener('keydown', handleKeyDown);
+    };
+  }, []);
+
   let view = null;
   if (didRecordCommits || selectedTabID === 'timeline') {
     switch (selectedTabID) {
@@ -112,7 +145,7 @@ function Profiler(_: {}) {
 
   return (
     <SettingsModalContextController>
-      <div className={styles.Profiler}>
+      <div ref={profilerRef} className={styles.Profiler}>
         <div className={styles.LeftColumn}>
           <div className={styles.Toolbar}>
             <RecordToggle disabled={!supportsProfiling} />

filename: packages/react-devtools-shared/src/devtools/views/Profiler/RecordToggle.js
patch: @@ -30,13 +30,19 @@ export default function RecordToggle({disabled}: Props): React.Node {
     className = styles.ActiveRecordToggle;
   }
 
+  const isMac =
+    typeof navigator !== 'undefined' &&
+    navigator.platform.toUpperCase().indexOf('MAC') >= 0;
+  const shortcut = isMac ? '⌘E' : 'Ctrl+E';
+  const title = `${isProfiling ? 'Stop' : 'Start'} profiling - ${shortcut}`;
+
   return (
     <Button
       className={className}
       disabled={disabled}
       onClick={isProfiling ? stopProfiling : startProfiling}
       testName="ProfilerToggleButton"
-      title={isProfiling ? 'Stop profiling' : 'Start profiling'}>
+      title={title}>
       <ButtonIcon type="record" />
     </Button>
   );