From 0ccd480a4d9f104ccf9fbe7d361ae9d581435a2f 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 | 60 ++++++++- 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 | 1 + 14 files changed, 213 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 be577ee3a9..3c37644397 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -421,8 +421,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 f5207c3488..135bd059d7 100644 --- a/src/components/shared/IdleSearchField.tsx +++ b/src/components/shared/IdleSearchField.tsx @@ -120,13 +120,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..0a9fbbc8d3 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,55 @@ 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; + } + 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 +95,10 @@ export class PanelSearch extends React.PureComponent { currentSearchString && !currentSearchString.includes(','); return ( -
+