From 94d55df1bcab4182e1cca87265b11f67b5171101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Thu, 14 May 2026 17:37:06 -0400 Subject: [PATCH] Add "/" and "f" keyboard shortcuts to focus the panel filter box "/" now focuses the filter input on any panel that has one (Call Tree, Flame Graph, Stack Chart, Marker Chart, Marker Table, Network Chart). The shortcut is discoverable through the input placeholder, which now reads "Enter filter terms (/)". "f" is an additional undocumented shortcut with the same effect, but only on panels where "f" is not already used as a call node transform shortcut (i.e. Marker Chart, Marker Table and Network Chart). It is opt-in via a new alsoFocusOnF prop on PanelSearch. Both shortcuts are no-ops when the user is already typing in an input, textarea or contenteditable element, and when modifier keys are held. --- locales/en-US/app.ftl | 7 +- src/components/shared/IdleSearchField.tsx | 4 +- src/components/shared/MarkerSettings.tsx | 1 + src/components/shared/NetworkSettings.tsx | 1 + src/components/shared/PanelSearch.tsx | 63 +++++++++- src/test/components/PanelSearch.test.tsx | 117 ++++++++++++++++++ .../__snapshots__/FlameGraph.test.tsx.snap | 2 +- .../__snapshots__/MarkerChart.test.tsx.snap | 2 +- .../__snapshots__/MarkerTable.test.tsx.snap | 2 +- .../__snapshots__/NetworkChart.test.tsx.snap | 2 +- .../ProfileCallTreeView.test.tsx.snap | 34 ++--- .../__snapshots__/StackChart.test.tsx.snap | 10 +- .../__snapshots__/StackSettings.test.tsx.snap | 2 +- src/test/fixtures/utils.ts | 3 + 14 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 src/test/components/PanelSearch.test.tsx diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl index 86409569e7..3697baf81e 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -428,8 +428,11 @@ Home--chrome-extension-recording-instructions = Once installed, use the extensio ## IdleSearchField ## The component that is used for all the search inputs in the application. -IdleSearchField--search-input = - .placeholder = Enter filter terms +# `/` here overrides Firefox's Type Ahead Find shortcut, which would +# otherwise trigger an unhelpful find bar on top of the profiler UI. +# The shortcut itself is not localizable. +IdleSearchField--search-input2 = + .placeholder = Enter filter terms (/) ## JsTracerSettings ## JSTracer is an experimental feature and it's currently disabled. See Bug 1565788. diff --git a/src/components/shared/IdleSearchField.tsx b/src/components/shared/IdleSearchField.tsx index 6df08478d5..03eb79d07d 100644 --- a/src/components/shared/IdleSearchField.tsx +++ b/src/components/shared/IdleSearchField.tsx @@ -122,13 +122,13 @@ export class IdleSearchField extends PureComponent { onSubmit={this._onFormSubmit} > { title="Only display markers that match a certain name" currentSearchString={searchString} onSearch={this._onSearch} + alsoFocusOnF={true} /> diff --git a/src/components/shared/NetworkSettings.tsx b/src/components/shared/NetworkSettings.tsx index fa29fe8ad1..670e1411bd 100644 --- a/src/components/shared/NetworkSettings.tsx +++ b/src/components/shared/NetworkSettings.tsx @@ -44,6 +44,7 @@ class NetworkSettingsImpl extends PureComponent { title="Only display network requests that match a certain name" currentSearchString={searchString} onSearch={this._onSearch} + alsoFocusOnF={true} /> diff --git a/src/components/shared/PanelSearch.tsx b/src/components/shared/PanelSearch.tsx index be4be2aef7..0d86ad2ace 100644 --- a/src/components/shared/PanelSearch.tsx +++ b/src/components/shared/PanelSearch.tsx @@ -14,12 +14,18 @@ type Props = { readonly title: string; readonly currentSearchString: string; readonly onSearch: (param: string) => void; + // When true, the "f" key (in addition to "/") will also focus the search + // field. This is opt-in because some panels (e.g. those showing frames) use + // "f" as a call node transform shortcut. + readonly alsoFocusOnF?: boolean; }; type State = { searchFieldFocused: boolean }; export class PanelSearch extends React.PureComponent { override state = { searchFieldFocused: false }; + _searchFieldWrapper = React.createRef(); + _onSearchFieldIdleAfterChange = (value: string) => { this.props.onSearch(value); }; @@ -32,6 +38,58 @@ export class PanelSearch extends React.PureComponent { this.setState(() => ({ searchFieldFocused: false })); }; + override componentDidMount() { + window.addEventListener('keydown', this._handleGlobalKeyDown); + } + + override componentWillUnmount() { + window.removeEventListener('keydown', this._handleGlobalKeyDown); + } + + _handleGlobalKeyDown = (event: KeyboardEvent) => { + // Ignore key combinations involving modifier keys, so we don't interfere + // with browser or OS shortcuts. + if (event.ctrlKey || event.altKey || event.metaKey) { + return; + } + + const isSlash = event.key === '/'; + const isF = this.props.alsoFocusOnF && event.key === 'f'; + if (!isSlash && !isF) { + return; + } + + // Don't steal the key when the user is already typing in a text input, + // textarea, contenteditable element, or interacting with a select. + const target = event.target; + if (target instanceof HTMLElement) { + const tagName = target.tagName; + if ( + tagName === 'INPUT' || + tagName === 'TEXTAREA' || + tagName === 'SELECT' || + target.isContentEditable + ) { + return; + } + } + + const wrapper = this._searchFieldWrapper.current; + if (!wrapper) { + return; + } + // This is a bit of a hack: we reach into the DOM to find the search input + // rather than holding a ref to it, because the input is rendered by a + // child component (IdleSearchField). See #6060 for a cleaner approach. + const input = wrapper.querySelector( + 'input[type="search"]' + ); + if (input) { + event.preventDefault(); + input.focus(); + } + }; + override render() { const { label, title, currentSearchString, className } = this.props; const { searchFieldFocused } = this.state; @@ -40,7 +98,10 @@ export class PanelSearch extends React.PureComponent { currentSearchString && !currentSearchString.includes(','); return ( -
+