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 ( -
+