xref: /aosp_15_r20/external/perfetto/ui/src/frontend/ui_main.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2023 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
16*6dbdd20aSAndroid Build Coastguard Workerimport {copyToClipboard} from '../base/clipboard';
17*6dbdd20aSAndroid Build Coastguard Workerimport {findRef} from '../base/dom_utils';
18*6dbdd20aSAndroid Build Coastguard Workerimport {FuzzyFinder} from '../base/fuzzy';
19*6dbdd20aSAndroid Build Coastguard Workerimport {assertExists, assertUnreachable} from '../base/logging';
20*6dbdd20aSAndroid Build Coastguard Workerimport {undoCommonChatAppReplacements} from '../base/string_utils';
21*6dbdd20aSAndroid Build Coastguard Workerimport {
22*6dbdd20aSAndroid Build Coastguard Worker  setDurationPrecision,
23*6dbdd20aSAndroid Build Coastguard Worker  setTimestampFormat,
24*6dbdd20aSAndroid Build Coastguard Worker} from '../core/timestamp_format';
25*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../core/raf_scheduler';
26*6dbdd20aSAndroid Build Coastguard Workerimport {Command} from '../public/command';
27*6dbdd20aSAndroid Build Coastguard Workerimport {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
28*6dbdd20aSAndroid Build Coastguard Workerimport {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
29*6dbdd20aSAndroid Build Coastguard Workerimport {maybeRenderFullscreenModalDialog, showModal} from '../widgets/modal';
30*6dbdd20aSAndroid Build Coastguard Workerimport {CookieConsent} from '../core/cookie_consent';
31*6dbdd20aSAndroid Build Coastguard Workerimport {toggleHelp} from './help_modal';
32*6dbdd20aSAndroid Build Coastguard Workerimport {Omnibox, OmniboxOption} from './omnibox';
33*6dbdd20aSAndroid Build Coastguard Workerimport {addQueryResultsTab} from '../components/query_table/query_result_tab';
34*6dbdd20aSAndroid Build Coastguard Workerimport {Sidebar} from './sidebar';
35*6dbdd20aSAndroid Build Coastguard Workerimport {Topbar} from './topbar';
36*6dbdd20aSAndroid Build Coastguard Workerimport {shareTrace} from './trace_share_utils';
37*6dbdd20aSAndroid Build Coastguard Workerimport {AggregationsTabs} from './aggregation_tab';
38*6dbdd20aSAndroid Build Coastguard Workerimport {OmniboxMode} from '../core/omnibox_manager';
39*6dbdd20aSAndroid Build Coastguard Workerimport {DisposableStack} from '../base/disposable_stack';
40*6dbdd20aSAndroid Build Coastguard Workerimport {Spinner} from '../widgets/spinner';
41*6dbdd20aSAndroid Build Coastguard Workerimport {TraceImpl} from '../core/trace_impl';
42*6dbdd20aSAndroid Build Coastguard Workerimport {AppImpl} from '../core/app_impl';
43*6dbdd20aSAndroid Build Coastguard Workerimport {NotesEditorTab} from './notes_panel';
44*6dbdd20aSAndroid Build Coastguard Workerimport {NotesListEditor} from './notes_list_editor';
45*6dbdd20aSAndroid Build Coastguard Workerimport {getTimeSpanOfSelectionOrVisibleWindow} from '../public/utils';
46*6dbdd20aSAndroid Build Coastguard Workerimport {DurationPrecision, TimestampFormat} from '../public/timeline';
47*6dbdd20aSAndroid Build Coastguard Worker
48*6dbdd20aSAndroid Build Coastguard Workerconst OMNIBOX_INPUT_REF = 'omnibox';
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Worker// This wrapper creates a new instance of UiMainPerTrace for each new trace
51*6dbdd20aSAndroid Build Coastguard Worker// loaded (including the case of no trace at the beginning).
52*6dbdd20aSAndroid Build Coastguard Workerexport class UiMain implements m.ClassComponent {
53*6dbdd20aSAndroid Build Coastguard Worker  view() {
54*6dbdd20aSAndroid Build Coastguard Worker    const currentTraceId = AppImpl.instance.trace?.engine.engineId ?? '';
55*6dbdd20aSAndroid Build Coastguard Worker    return [m(UiMainPerTrace, {key: currentTraceId})];
56*6dbdd20aSAndroid Build Coastguard Worker  }
57*6dbdd20aSAndroid Build Coastguard Worker}
58*6dbdd20aSAndroid Build Coastguard Worker
59*6dbdd20aSAndroid Build Coastguard Worker// This components gets destroyed and recreated every time the current trace
60*6dbdd20aSAndroid Build Coastguard Worker// changes. Note that in the beginning the current trace is undefined.
61*6dbdd20aSAndroid Build Coastguard Workerexport class UiMainPerTrace implements m.ClassComponent {
62*6dbdd20aSAndroid Build Coastguard Worker  // NOTE: this should NOT need to be an AsyncDisposableStack. If you feel the
63*6dbdd20aSAndroid Build Coastguard Worker  // need of making it async because you want to clean up SQL resources, that
64*6dbdd20aSAndroid Build Coastguard Worker  // will cause bugs (see comments in oncreate()).
65*6dbdd20aSAndroid Build Coastguard Worker  private trash = new DisposableStack();
66*6dbdd20aSAndroid Build Coastguard Worker  private omniboxInputEl?: HTMLInputElement;
67*6dbdd20aSAndroid Build Coastguard Worker  private recentCommands: string[] = [];
68*6dbdd20aSAndroid Build Coastguard Worker  private trace?: TraceImpl;
69*6dbdd20aSAndroid Build Coastguard Worker
70*6dbdd20aSAndroid Build Coastguard Worker  // This function is invoked once per trace.
71*6dbdd20aSAndroid Build Coastguard Worker  constructor() {
72*6dbdd20aSAndroid Build Coastguard Worker    const app = AppImpl.instance;
73*6dbdd20aSAndroid Build Coastguard Worker    const trace = app.trace;
74*6dbdd20aSAndroid Build Coastguard Worker    this.trace = trace;
75*6dbdd20aSAndroid Build Coastguard Worker
76*6dbdd20aSAndroid Build Coastguard Worker    // Register global commands (commands that are useful even without a trace
77*6dbdd20aSAndroid Build Coastguard Worker    // loaded).
78*6dbdd20aSAndroid Build Coastguard Worker    const globalCmds: Command[] = [
79*6dbdd20aSAndroid Build Coastguard Worker      {
80*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.OpenCommandPalette',
81*6dbdd20aSAndroid Build Coastguard Worker        name: 'Open command palette',
82*6dbdd20aSAndroid Build Coastguard Worker        callback: () => app.omnibox.setMode(OmniboxMode.Command),
83*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: '!Mod+Shift+P',
84*6dbdd20aSAndroid Build Coastguard Worker      },
85*6dbdd20aSAndroid Build Coastguard Worker
86*6dbdd20aSAndroid Build Coastguard Worker      {
87*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.ShowHelp',
88*6dbdd20aSAndroid Build Coastguard Worker        name: 'Show help',
89*6dbdd20aSAndroid Build Coastguard Worker        callback: () => toggleHelp(),
90*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: '?',
91*6dbdd20aSAndroid Build Coastguard Worker      },
92*6dbdd20aSAndroid Build Coastguard Worker    ];
93*6dbdd20aSAndroid Build Coastguard Worker    globalCmds.forEach((cmd) => {
94*6dbdd20aSAndroid Build Coastguard Worker      this.trash.use(app.commands.registerCommand(cmd));
95*6dbdd20aSAndroid Build Coastguard Worker    });
96*6dbdd20aSAndroid Build Coastguard Worker
97*6dbdd20aSAndroid Build Coastguard Worker    // When the UI loads there is no trace. There is no point registering
98*6dbdd20aSAndroid Build Coastguard Worker    // commands or anything in this state as they will be useless.
99*6dbdd20aSAndroid Build Coastguard Worker    if (trace === undefined) return;
100*6dbdd20aSAndroid Build Coastguard Worker    document.title = `${trace.traceInfo.traceTitle || 'Trace'} - Perfetto UI`;
101*6dbdd20aSAndroid Build Coastguard Worker    this.maybeShowJsonWarning();
102*6dbdd20aSAndroid Build Coastguard Worker
103*6dbdd20aSAndroid Build Coastguard Worker    // Register the aggregation tabs.
104*6dbdd20aSAndroid Build Coastguard Worker    this.trash.use(new AggregationsTabs(trace));
105*6dbdd20aSAndroid Build Coastguard Worker
106*6dbdd20aSAndroid Build Coastguard Worker    // Register the notes manager+editor.
107*6dbdd20aSAndroid Build Coastguard Worker    this.trash.use(trace.tabs.registerDetailsPanel(new NotesEditorTab(trace)));
108*6dbdd20aSAndroid Build Coastguard Worker
109*6dbdd20aSAndroid Build Coastguard Worker    this.trash.use(
110*6dbdd20aSAndroid Build Coastguard Worker      trace.tabs.registerTab({
111*6dbdd20aSAndroid Build Coastguard Worker        uri: 'notes.manager',
112*6dbdd20aSAndroid Build Coastguard Worker        isEphemeral: false,
113*6dbdd20aSAndroid Build Coastguard Worker        content: {
114*6dbdd20aSAndroid Build Coastguard Worker          getTitle: () => 'Notes & markers',
115*6dbdd20aSAndroid Build Coastguard Worker          render: () => m(NotesListEditor, {trace}),
116*6dbdd20aSAndroid Build Coastguard Worker        },
117*6dbdd20aSAndroid Build Coastguard Worker      }),
118*6dbdd20aSAndroid Build Coastguard Worker    );
119*6dbdd20aSAndroid Build Coastguard Worker
120*6dbdd20aSAndroid Build Coastguard Worker    const cmds: Command[] = [
121*6dbdd20aSAndroid Build Coastguard Worker      {
122*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SetTimestampFormat',
123*6dbdd20aSAndroid Build Coastguard Worker        name: 'Set timestamp and duration format',
124*6dbdd20aSAndroid Build Coastguard Worker        callback: async () => {
125*6dbdd20aSAndroid Build Coastguard Worker          const TF = TimestampFormat;
126*6dbdd20aSAndroid Build Coastguard Worker          const result = await app.omnibox.prompt('Select format...', {
127*6dbdd20aSAndroid Build Coastguard Worker            values: [
128*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.Timecode, name: 'Timecode'},
129*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.UTC, name: 'Realtime (UTC)'},
130*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.TraceTz, name: 'Realtime (Trace TZ)'},
131*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.Seconds, name: 'Seconds'},
132*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.Milliseconds, name: 'Milliseconds'},
133*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.Microseconds, name: 'Microseconds'},
134*6dbdd20aSAndroid Build Coastguard Worker              {format: TF.TraceNs, name: 'Trace nanoseconds'},
135*6dbdd20aSAndroid Build Coastguard Worker              {
136*6dbdd20aSAndroid Build Coastguard Worker                format: TF.TraceNsLocale,
137*6dbdd20aSAndroid Build Coastguard Worker                name: 'Trace nanoseconds (with locale-specific formatting)',
138*6dbdd20aSAndroid Build Coastguard Worker              },
139*6dbdd20aSAndroid Build Coastguard Worker            ],
140*6dbdd20aSAndroid Build Coastguard Worker            getName: (x) => x.name,
141*6dbdd20aSAndroid Build Coastguard Worker          });
142*6dbdd20aSAndroid Build Coastguard Worker          result && setTimestampFormat(result.format);
143*6dbdd20aSAndroid Build Coastguard Worker          raf.scheduleFullRedraw();
144*6dbdd20aSAndroid Build Coastguard Worker        },
145*6dbdd20aSAndroid Build Coastguard Worker      },
146*6dbdd20aSAndroid Build Coastguard Worker      {
147*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SetDurationPrecision',
148*6dbdd20aSAndroid Build Coastguard Worker        name: 'Set duration precision',
149*6dbdd20aSAndroid Build Coastguard Worker        callback: async () => {
150*6dbdd20aSAndroid Build Coastguard Worker          const DF = DurationPrecision;
151*6dbdd20aSAndroid Build Coastguard Worker          const result = await app.omnibox.prompt(
152*6dbdd20aSAndroid Build Coastguard Worker            'Select duration precision mode...',
153*6dbdd20aSAndroid Build Coastguard Worker            {
154*6dbdd20aSAndroid Build Coastguard Worker              values: [
155*6dbdd20aSAndroid Build Coastguard Worker                {format: DF.Full, name: 'Full'},
156*6dbdd20aSAndroid Build Coastguard Worker                {format: DF.HumanReadable, name: 'Human readable'},
157*6dbdd20aSAndroid Build Coastguard Worker              ],
158*6dbdd20aSAndroid Build Coastguard Worker              getName: (x) => x.name,
159*6dbdd20aSAndroid Build Coastguard Worker            },
160*6dbdd20aSAndroid Build Coastguard Worker          );
161*6dbdd20aSAndroid Build Coastguard Worker          result && setDurationPrecision(result.format);
162*6dbdd20aSAndroid Build Coastguard Worker          raf.scheduleFullRedraw();
163*6dbdd20aSAndroid Build Coastguard Worker        },
164*6dbdd20aSAndroid Build Coastguard Worker      },
165*6dbdd20aSAndroid Build Coastguard Worker      {
166*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.TogglePerformanceMetrics',
167*6dbdd20aSAndroid Build Coastguard Worker        name: 'Toggle performance metrics',
168*6dbdd20aSAndroid Build Coastguard Worker        callback: () =>
169*6dbdd20aSAndroid Build Coastguard Worker          (app.perfDebugging.enabled = !app.perfDebugging.enabled),
170*6dbdd20aSAndroid Build Coastguard Worker      },
171*6dbdd20aSAndroid Build Coastguard Worker      {
172*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.ShareTrace',
173*6dbdd20aSAndroid Build Coastguard Worker        name: 'Share trace',
174*6dbdd20aSAndroid Build Coastguard Worker        callback: shareTrace,
175*6dbdd20aSAndroid Build Coastguard Worker      },
176*6dbdd20aSAndroid Build Coastguard Worker      {
177*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SearchNext',
178*6dbdd20aSAndroid Build Coastguard Worker        name: 'Go to next search result',
179*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
180*6dbdd20aSAndroid Build Coastguard Worker          trace.search.stepForward();
181*6dbdd20aSAndroid Build Coastguard Worker        },
182*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Enter',
183*6dbdd20aSAndroid Build Coastguard Worker      },
184*6dbdd20aSAndroid Build Coastguard Worker      {
185*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SearchPrev',
186*6dbdd20aSAndroid Build Coastguard Worker        name: 'Go to previous search result',
187*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
188*6dbdd20aSAndroid Build Coastguard Worker          trace.search.stepBackwards();
189*6dbdd20aSAndroid Build Coastguard Worker        },
190*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Shift+Enter',
191*6dbdd20aSAndroid Build Coastguard Worker      },
192*6dbdd20aSAndroid Build Coastguard Worker      {
193*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.RunQuery',
194*6dbdd20aSAndroid Build Coastguard Worker        name: 'Run query',
195*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.omnibox.setMode(OmniboxMode.Query),
196*6dbdd20aSAndroid Build Coastguard Worker      },
197*6dbdd20aSAndroid Build Coastguard Worker      {
198*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.Search',
199*6dbdd20aSAndroid Build Coastguard Worker        name: 'Search',
200*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.omnibox.setMode(OmniboxMode.Search),
201*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: '/',
202*6dbdd20aSAndroid Build Coastguard Worker      },
203*6dbdd20aSAndroid Build Coastguard Worker      {
204*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.CopyTimeWindow',
205*6dbdd20aSAndroid Build Coastguard Worker        name: `Copy selected time window to clipboard`,
206*6dbdd20aSAndroid Build Coastguard Worker        callback: async () => {
207*6dbdd20aSAndroid Build Coastguard Worker          const window = await getTimeSpanOfSelectionOrVisibleWindow(trace);
208*6dbdd20aSAndroid Build Coastguard Worker          const query = `ts >= ${window.start} and ts < ${window.end}`;
209*6dbdd20aSAndroid Build Coastguard Worker          copyToClipboard(query);
210*6dbdd20aSAndroid Build Coastguard Worker        },
211*6dbdd20aSAndroid Build Coastguard Worker      },
212*6dbdd20aSAndroid Build Coastguard Worker      {
213*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.FocusSelection',
214*6dbdd20aSAndroid Build Coastguard Worker        name: 'Focus current selection',
215*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.selection.scrollToCurrentSelection(),
216*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'F',
217*6dbdd20aSAndroid Build Coastguard Worker      },
218*6dbdd20aSAndroid Build Coastguard Worker      {
219*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.Deselect',
220*6dbdd20aSAndroid Build Coastguard Worker        name: 'Deselect',
221*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
222*6dbdd20aSAndroid Build Coastguard Worker          trace.selection.clear();
223*6dbdd20aSAndroid Build Coastguard Worker        },
224*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Escape',
225*6dbdd20aSAndroid Build Coastguard Worker      },
226*6dbdd20aSAndroid Build Coastguard Worker      {
227*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SetTemporarySpanNote',
228*6dbdd20aSAndroid Build Coastguard Worker        name: 'Set the temporary span note based on the current selection',
229*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
230*6dbdd20aSAndroid Build Coastguard Worker          const range = trace.selection.findTimeRangeOfSelection();
231*6dbdd20aSAndroid Build Coastguard Worker          if (range) {
232*6dbdd20aSAndroid Build Coastguard Worker            trace.notes.addSpanNote({
233*6dbdd20aSAndroid Build Coastguard Worker              start: range.start,
234*6dbdd20aSAndroid Build Coastguard Worker              end: range.end,
235*6dbdd20aSAndroid Build Coastguard Worker              id: '__temp__',
236*6dbdd20aSAndroid Build Coastguard Worker            });
237*6dbdd20aSAndroid Build Coastguard Worker
238*6dbdd20aSAndroid Build Coastguard Worker            // Also select an area for this span
239*6dbdd20aSAndroid Build Coastguard Worker            const selection = trace.selection.selection;
240*6dbdd20aSAndroid Build Coastguard Worker            if (selection.kind === 'track_event') {
241*6dbdd20aSAndroid Build Coastguard Worker              trace.selection.selectArea({
242*6dbdd20aSAndroid Build Coastguard Worker                start: range.start,
243*6dbdd20aSAndroid Build Coastguard Worker                end: range.end,
244*6dbdd20aSAndroid Build Coastguard Worker                trackUris: [selection.trackUri],
245*6dbdd20aSAndroid Build Coastguard Worker              });
246*6dbdd20aSAndroid Build Coastguard Worker            }
247*6dbdd20aSAndroid Build Coastguard Worker          }
248*6dbdd20aSAndroid Build Coastguard Worker        },
249*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'M',
250*6dbdd20aSAndroid Build Coastguard Worker      },
251*6dbdd20aSAndroid Build Coastguard Worker      {
252*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.AddSpanNote',
253*6dbdd20aSAndroid Build Coastguard Worker        name: 'Add a new span note based on the current selection',
254*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
255*6dbdd20aSAndroid Build Coastguard Worker          const range = trace.selection.findTimeRangeOfSelection();
256*6dbdd20aSAndroid Build Coastguard Worker          if (range) {
257*6dbdd20aSAndroid Build Coastguard Worker            trace.notes.addSpanNote({
258*6dbdd20aSAndroid Build Coastguard Worker              start: range.start,
259*6dbdd20aSAndroid Build Coastguard Worker              end: range.end,
260*6dbdd20aSAndroid Build Coastguard Worker            });
261*6dbdd20aSAndroid Build Coastguard Worker          }
262*6dbdd20aSAndroid Build Coastguard Worker        },
263*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Shift+M',
264*6dbdd20aSAndroid Build Coastguard Worker      },
265*6dbdd20aSAndroid Build Coastguard Worker      {
266*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.RemoveSelectedNote',
267*6dbdd20aSAndroid Build Coastguard Worker        name: 'Remove selected note',
268*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
269*6dbdd20aSAndroid Build Coastguard Worker          const selection = trace.selection.selection;
270*6dbdd20aSAndroid Build Coastguard Worker          if (selection.kind === 'note') {
271*6dbdd20aSAndroid Build Coastguard Worker            trace.notes.removeNote(selection.id);
272*6dbdd20aSAndroid Build Coastguard Worker          }
273*6dbdd20aSAndroid Build Coastguard Worker        },
274*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Delete',
275*6dbdd20aSAndroid Build Coastguard Worker      },
276*6dbdd20aSAndroid Build Coastguard Worker      {
277*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.NextFlow',
278*6dbdd20aSAndroid Build Coastguard Worker        name: 'Next flow',
279*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.flows.focusOtherFlow('Forward'),
280*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Mod+]',
281*6dbdd20aSAndroid Build Coastguard Worker      },
282*6dbdd20aSAndroid Build Coastguard Worker      {
283*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.PrevFlow',
284*6dbdd20aSAndroid Build Coastguard Worker        name: 'Prev flow',
285*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.flows.focusOtherFlow('Backward'),
286*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Mod+[',
287*6dbdd20aSAndroid Build Coastguard Worker      },
288*6dbdd20aSAndroid Build Coastguard Worker      {
289*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.MoveNextFlow',
290*6dbdd20aSAndroid Build Coastguard Worker        name: 'Move next flow',
291*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.flows.moveByFocusedFlow('Forward'),
292*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: ']',
293*6dbdd20aSAndroid Build Coastguard Worker      },
294*6dbdd20aSAndroid Build Coastguard Worker      {
295*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.MovePrevFlow',
296*6dbdd20aSAndroid Build Coastguard Worker        name: 'Move prev flow',
297*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.flows.moveByFocusedFlow('Backward'),
298*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: '[',
299*6dbdd20aSAndroid Build Coastguard Worker      },
300*6dbdd20aSAndroid Build Coastguard Worker      {
301*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.SelectAll',
302*6dbdd20aSAndroid Build Coastguard Worker        name: 'Select all',
303*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
304*6dbdd20aSAndroid Build Coastguard Worker          // This is a dual state command:
305*6dbdd20aSAndroid Build Coastguard Worker          // - If one ore more tracks are already area selected, expand the time
306*6dbdd20aSAndroid Build Coastguard Worker          //   range to include the entire trace, but keep the selection on just
307*6dbdd20aSAndroid Build Coastguard Worker          //   these tracks.
308*6dbdd20aSAndroid Build Coastguard Worker          // - If nothing is selected, or all selected tracks are entirely
309*6dbdd20aSAndroid Build Coastguard Worker          //   selected, then select the entire trace. This allows double tapping
310*6dbdd20aSAndroid Build Coastguard Worker          //   Ctrl+A to select the entire track, then select the entire trace.
311*6dbdd20aSAndroid Build Coastguard Worker          let tracksToSelect: string[];
312*6dbdd20aSAndroid Build Coastguard Worker          const selection = trace.selection.selection;
313*6dbdd20aSAndroid Build Coastguard Worker          if (selection.kind === 'area') {
314*6dbdd20aSAndroid Build Coastguard Worker            // Something is already selected, let's see if it covers the entire
315*6dbdd20aSAndroid Build Coastguard Worker            // span of the trace or not
316*6dbdd20aSAndroid Build Coastguard Worker            const coversEntireTimeRange =
317*6dbdd20aSAndroid Build Coastguard Worker              trace.traceInfo.start === selection.start &&
318*6dbdd20aSAndroid Build Coastguard Worker              trace.traceInfo.end === selection.end;
319*6dbdd20aSAndroid Build Coastguard Worker            if (!coversEntireTimeRange) {
320*6dbdd20aSAndroid Build Coastguard Worker              // If the current selection is an area which does not cover the
321*6dbdd20aSAndroid Build Coastguard Worker              // entire time range, preserve the list of selected tracks and
322*6dbdd20aSAndroid Build Coastguard Worker              // expand the time range.
323*6dbdd20aSAndroid Build Coastguard Worker              tracksToSelect = selection.trackUris;
324*6dbdd20aSAndroid Build Coastguard Worker            } else {
325*6dbdd20aSAndroid Build Coastguard Worker              // If the entire time range is already covered, update the selection
326*6dbdd20aSAndroid Build Coastguard Worker              // to cover all tracks.
327*6dbdd20aSAndroid Build Coastguard Worker              tracksToSelect = trace.workspace.flatTracks
328*6dbdd20aSAndroid Build Coastguard Worker                .map((t) => t.uri)
329*6dbdd20aSAndroid Build Coastguard Worker                .filter((uri) => uri !== undefined);
330*6dbdd20aSAndroid Build Coastguard Worker            }
331*6dbdd20aSAndroid Build Coastguard Worker          } else {
332*6dbdd20aSAndroid Build Coastguard Worker            // If the current selection is not an area, select all.
333*6dbdd20aSAndroid Build Coastguard Worker            tracksToSelect = trace.workspace.flatTracks
334*6dbdd20aSAndroid Build Coastguard Worker              .map((t) => t.uri)
335*6dbdd20aSAndroid Build Coastguard Worker              .filter((uri) => uri !== undefined);
336*6dbdd20aSAndroid Build Coastguard Worker          }
337*6dbdd20aSAndroid Build Coastguard Worker          const {start, end} = trace.traceInfo;
338*6dbdd20aSAndroid Build Coastguard Worker          trace.selection.selectArea({
339*6dbdd20aSAndroid Build Coastguard Worker            start,
340*6dbdd20aSAndroid Build Coastguard Worker            end,
341*6dbdd20aSAndroid Build Coastguard Worker            trackUris: tracksToSelect,
342*6dbdd20aSAndroid Build Coastguard Worker          });
343*6dbdd20aSAndroid Build Coastguard Worker        },
344*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Mod+A',
345*6dbdd20aSAndroid Build Coastguard Worker      },
346*6dbdd20aSAndroid Build Coastguard Worker      {
347*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.ConvertSelectionToArea',
348*6dbdd20aSAndroid Build Coastguard Worker        name: 'Convert the current selection to an area selection',
349*6dbdd20aSAndroid Build Coastguard Worker        callback: () => {
350*6dbdd20aSAndroid Build Coastguard Worker          const selection = trace.selection.selection;
351*6dbdd20aSAndroid Build Coastguard Worker          const range = trace.selection.findTimeRangeOfSelection();
352*6dbdd20aSAndroid Build Coastguard Worker          if (selection.kind === 'track_event' && range) {
353*6dbdd20aSAndroid Build Coastguard Worker            trace.selection.selectArea({
354*6dbdd20aSAndroid Build Coastguard Worker              start: range.start,
355*6dbdd20aSAndroid Build Coastguard Worker              end: range.end,
356*6dbdd20aSAndroid Build Coastguard Worker              trackUris: [selection.trackUri],
357*6dbdd20aSAndroid Build Coastguard Worker            });
358*6dbdd20aSAndroid Build Coastguard Worker          }
359*6dbdd20aSAndroid Build Coastguard Worker        },
360*6dbdd20aSAndroid Build Coastguard Worker        // TODO(stevegolton): Decide on a sensible hotkey.
361*6dbdd20aSAndroid Build Coastguard Worker        // defaultHotkey: 'L',
362*6dbdd20aSAndroid Build Coastguard Worker      },
363*6dbdd20aSAndroid Build Coastguard Worker      {
364*6dbdd20aSAndroid Build Coastguard Worker        id: 'perfetto.ToggleDrawer',
365*6dbdd20aSAndroid Build Coastguard Worker        name: 'Toggle drawer',
366*6dbdd20aSAndroid Build Coastguard Worker        defaultHotkey: 'Q',
367*6dbdd20aSAndroid Build Coastguard Worker        callback: () => trace.tabs.toggleTabPanelVisibility(),
368*6dbdd20aSAndroid Build Coastguard Worker      },
369*6dbdd20aSAndroid Build Coastguard Worker    ];
370*6dbdd20aSAndroid Build Coastguard Worker
371*6dbdd20aSAndroid Build Coastguard Worker    // Register each command with the command manager
372*6dbdd20aSAndroid Build Coastguard Worker    cmds.forEach((cmd) => {
373*6dbdd20aSAndroid Build Coastguard Worker      this.trash.use(trace.commands.registerCommand(cmd));
374*6dbdd20aSAndroid Build Coastguard Worker    });
375*6dbdd20aSAndroid Build Coastguard Worker  }
376*6dbdd20aSAndroid Build Coastguard Worker
377*6dbdd20aSAndroid Build Coastguard Worker  private renderOmnibox(): m.Children {
378*6dbdd20aSAndroid Build Coastguard Worker    const omnibox = AppImpl.instance.omnibox;
379*6dbdd20aSAndroid Build Coastguard Worker    const omniboxMode = omnibox.mode;
380*6dbdd20aSAndroid Build Coastguard Worker    const statusMessage = omnibox.statusMessage;
381*6dbdd20aSAndroid Build Coastguard Worker    if (statusMessage !== undefined) {
382*6dbdd20aSAndroid Build Coastguard Worker      return m(
383*6dbdd20aSAndroid Build Coastguard Worker        `.omnibox.message-mode`,
384*6dbdd20aSAndroid Build Coastguard Worker        m(`input[readonly][disabled][ref=omnibox]`, {
385*6dbdd20aSAndroid Build Coastguard Worker          value: '',
386*6dbdd20aSAndroid Build Coastguard Worker          placeholder: statusMessage,
387*6dbdd20aSAndroid Build Coastguard Worker        }),
388*6dbdd20aSAndroid Build Coastguard Worker      );
389*6dbdd20aSAndroid Build Coastguard Worker    } else if (omniboxMode === OmniboxMode.Command) {
390*6dbdd20aSAndroid Build Coastguard Worker      return this.renderCommandOmnibox();
391*6dbdd20aSAndroid Build Coastguard Worker    } else if (omniboxMode === OmniboxMode.Prompt) {
392*6dbdd20aSAndroid Build Coastguard Worker      return this.renderPromptOmnibox();
393*6dbdd20aSAndroid Build Coastguard Worker    } else if (omniboxMode === OmniboxMode.Query) {
394*6dbdd20aSAndroid Build Coastguard Worker      return this.renderQueryOmnibox();
395*6dbdd20aSAndroid Build Coastguard Worker    } else if (omniboxMode === OmniboxMode.Search) {
396*6dbdd20aSAndroid Build Coastguard Worker      return this.renderSearchOmnibox();
397*6dbdd20aSAndroid Build Coastguard Worker    } else {
398*6dbdd20aSAndroid Build Coastguard Worker      assertUnreachable(omniboxMode);
399*6dbdd20aSAndroid Build Coastguard Worker    }
400*6dbdd20aSAndroid Build Coastguard Worker  }
401*6dbdd20aSAndroid Build Coastguard Worker
402*6dbdd20aSAndroid Build Coastguard Worker  renderPromptOmnibox(): m.Children {
403*6dbdd20aSAndroid Build Coastguard Worker    const omnibox = AppImpl.instance.omnibox;
404*6dbdd20aSAndroid Build Coastguard Worker    const prompt = assertExists(omnibox.pendingPrompt);
405*6dbdd20aSAndroid Build Coastguard Worker
406*6dbdd20aSAndroid Build Coastguard Worker    let options: OmniboxOption[] | undefined = undefined;
407*6dbdd20aSAndroid Build Coastguard Worker
408*6dbdd20aSAndroid Build Coastguard Worker    if (prompt.options) {
409*6dbdd20aSAndroid Build Coastguard Worker      const fuzzy = new FuzzyFinder(
410*6dbdd20aSAndroid Build Coastguard Worker        prompt.options,
411*6dbdd20aSAndroid Build Coastguard Worker        ({displayName}) => displayName,
412*6dbdd20aSAndroid Build Coastguard Worker      );
413*6dbdd20aSAndroid Build Coastguard Worker      const result = fuzzy.find(omnibox.text);
414*6dbdd20aSAndroid Build Coastguard Worker      options = result.map((result) => {
415*6dbdd20aSAndroid Build Coastguard Worker        return {
416*6dbdd20aSAndroid Build Coastguard Worker          key: result.item.key,
417*6dbdd20aSAndroid Build Coastguard Worker          displayName: result.segments,
418*6dbdd20aSAndroid Build Coastguard Worker        };
419*6dbdd20aSAndroid Build Coastguard Worker      });
420*6dbdd20aSAndroid Build Coastguard Worker    }
421*6dbdd20aSAndroid Build Coastguard Worker
422*6dbdd20aSAndroid Build Coastguard Worker    return m(Omnibox, {
423*6dbdd20aSAndroid Build Coastguard Worker      value: omnibox.text,
424*6dbdd20aSAndroid Build Coastguard Worker      placeholder: prompt.text,
425*6dbdd20aSAndroid Build Coastguard Worker      inputRef: OMNIBOX_INPUT_REF,
426*6dbdd20aSAndroid Build Coastguard Worker      extraClasses: 'prompt-mode',
427*6dbdd20aSAndroid Build Coastguard Worker      closeOnOutsideClick: true,
428*6dbdd20aSAndroid Build Coastguard Worker      options,
429*6dbdd20aSAndroid Build Coastguard Worker      selectedOptionIndex: omnibox.selectionIndex,
430*6dbdd20aSAndroid Build Coastguard Worker      onSelectedOptionChanged: (index) => {
431*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setSelectionIndex(index);
432*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
433*6dbdd20aSAndroid Build Coastguard Worker      },
434*6dbdd20aSAndroid Build Coastguard Worker      onInput: (value) => {
435*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setText(value);
436*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setSelectionIndex(0);
437*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
438*6dbdd20aSAndroid Build Coastguard Worker      },
439*6dbdd20aSAndroid Build Coastguard Worker      onSubmit: (value, _alt) => {
440*6dbdd20aSAndroid Build Coastguard Worker        omnibox.resolvePrompt(value);
441*6dbdd20aSAndroid Build Coastguard Worker      },
442*6dbdd20aSAndroid Build Coastguard Worker      onClose: () => {
443*6dbdd20aSAndroid Build Coastguard Worker        omnibox.rejectPrompt();
444*6dbdd20aSAndroid Build Coastguard Worker      },
445*6dbdd20aSAndroid Build Coastguard Worker    });
446*6dbdd20aSAndroid Build Coastguard Worker  }
447*6dbdd20aSAndroid Build Coastguard Worker
448*6dbdd20aSAndroid Build Coastguard Worker  renderCommandOmnibox(): m.Children {
449*6dbdd20aSAndroid Build Coastguard Worker    // Fuzzy-filter commands by the filter string.
450*6dbdd20aSAndroid Build Coastguard Worker    const {commands, omnibox} = AppImpl.instance;
451*6dbdd20aSAndroid Build Coastguard Worker    const filteredCmds = commands.fuzzyFilterCommands(omnibox.text);
452*6dbdd20aSAndroid Build Coastguard Worker
453*6dbdd20aSAndroid Build Coastguard Worker    // Create an array of commands with attached heuristics from the recent
454*6dbdd20aSAndroid Build Coastguard Worker    // command register.
455*6dbdd20aSAndroid Build Coastguard Worker    const commandsWithHeuristics = filteredCmds.map((cmd) => {
456*6dbdd20aSAndroid Build Coastguard Worker      return {
457*6dbdd20aSAndroid Build Coastguard Worker        recentsIndex: this.recentCommands.findIndex((id) => id === cmd.id),
458*6dbdd20aSAndroid Build Coastguard Worker        cmd,
459*6dbdd20aSAndroid Build Coastguard Worker      };
460*6dbdd20aSAndroid Build Coastguard Worker    });
461*6dbdd20aSAndroid Build Coastguard Worker
462*6dbdd20aSAndroid Build Coastguard Worker    // Sort recentsIndex first
463*6dbdd20aSAndroid Build Coastguard Worker    const sorted = commandsWithHeuristics.sort((a, b) => {
464*6dbdd20aSAndroid Build Coastguard Worker      if (b.recentsIndex === a.recentsIndex) {
465*6dbdd20aSAndroid Build Coastguard Worker        // If recentsIndex is the same, retain original sort order
466*6dbdd20aSAndroid Build Coastguard Worker        return 0;
467*6dbdd20aSAndroid Build Coastguard Worker      } else {
468*6dbdd20aSAndroid Build Coastguard Worker        return b.recentsIndex - a.recentsIndex;
469*6dbdd20aSAndroid Build Coastguard Worker      }
470*6dbdd20aSAndroid Build Coastguard Worker    });
471*6dbdd20aSAndroid Build Coastguard Worker
472*6dbdd20aSAndroid Build Coastguard Worker    const options = sorted.map(({recentsIndex, cmd}): OmniboxOption => {
473*6dbdd20aSAndroid Build Coastguard Worker      const {segments, id, defaultHotkey} = cmd;
474*6dbdd20aSAndroid Build Coastguard Worker      return {
475*6dbdd20aSAndroid Build Coastguard Worker        key: id,
476*6dbdd20aSAndroid Build Coastguard Worker        displayName: segments,
477*6dbdd20aSAndroid Build Coastguard Worker        tag: recentsIndex !== -1 ? 'recently used' : undefined,
478*6dbdd20aSAndroid Build Coastguard Worker        rightContent: defaultHotkey && m(HotkeyGlyphs, {hotkey: defaultHotkey}),
479*6dbdd20aSAndroid Build Coastguard Worker      };
480*6dbdd20aSAndroid Build Coastguard Worker    });
481*6dbdd20aSAndroid Build Coastguard Worker
482*6dbdd20aSAndroid Build Coastguard Worker    return m(Omnibox, {
483*6dbdd20aSAndroid Build Coastguard Worker      value: omnibox.text,
484*6dbdd20aSAndroid Build Coastguard Worker      placeholder: 'Filter commands...',
485*6dbdd20aSAndroid Build Coastguard Worker      inputRef: OMNIBOX_INPUT_REF,
486*6dbdd20aSAndroid Build Coastguard Worker      extraClasses: 'command-mode',
487*6dbdd20aSAndroid Build Coastguard Worker      options,
488*6dbdd20aSAndroid Build Coastguard Worker      closeOnSubmit: true,
489*6dbdd20aSAndroid Build Coastguard Worker      closeOnOutsideClick: true,
490*6dbdd20aSAndroid Build Coastguard Worker      selectedOptionIndex: omnibox.selectionIndex,
491*6dbdd20aSAndroid Build Coastguard Worker      onSelectedOptionChanged: (index) => {
492*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setSelectionIndex(index);
493*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
494*6dbdd20aSAndroid Build Coastguard Worker      },
495*6dbdd20aSAndroid Build Coastguard Worker      onInput: (value) => {
496*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setText(value);
497*6dbdd20aSAndroid Build Coastguard Worker        omnibox.setSelectionIndex(0);
498*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
499*6dbdd20aSAndroid Build Coastguard Worker      },
500*6dbdd20aSAndroid Build Coastguard Worker      onClose: () => {
501*6dbdd20aSAndroid Build Coastguard Worker        if (this.omniboxInputEl) {
502*6dbdd20aSAndroid Build Coastguard Worker          this.omniboxInputEl.blur();
503*6dbdd20aSAndroid Build Coastguard Worker        }
504*6dbdd20aSAndroid Build Coastguard Worker        omnibox.reset();
505*6dbdd20aSAndroid Build Coastguard Worker      },
506*6dbdd20aSAndroid Build Coastguard Worker      onSubmit: (key: string) => {
507*6dbdd20aSAndroid Build Coastguard Worker        this.addRecentCommand(key);
508*6dbdd20aSAndroid Build Coastguard Worker        commands.runCommand(key);
509*6dbdd20aSAndroid Build Coastguard Worker      },
510*6dbdd20aSAndroid Build Coastguard Worker      onGoBack: () => {
511*6dbdd20aSAndroid Build Coastguard Worker        omnibox.reset();
512*6dbdd20aSAndroid Build Coastguard Worker      },
513*6dbdd20aSAndroid Build Coastguard Worker    });
514*6dbdd20aSAndroid Build Coastguard Worker  }
515*6dbdd20aSAndroid Build Coastguard Worker
516*6dbdd20aSAndroid Build Coastguard Worker  private addRecentCommand(id: string): void {
517*6dbdd20aSAndroid Build Coastguard Worker    this.recentCommands = this.recentCommands.filter((x) => x !== id);
518*6dbdd20aSAndroid Build Coastguard Worker    this.recentCommands.push(id);
519*6dbdd20aSAndroid Build Coastguard Worker    while (this.recentCommands.length > 6) {
520*6dbdd20aSAndroid Build Coastguard Worker      this.recentCommands.shift();
521*6dbdd20aSAndroid Build Coastguard Worker    }
522*6dbdd20aSAndroid Build Coastguard Worker  }
523*6dbdd20aSAndroid Build Coastguard Worker
524*6dbdd20aSAndroid Build Coastguard Worker  renderQueryOmnibox(): m.Children {
525*6dbdd20aSAndroid Build Coastguard Worker    const ph = 'e.g. select * from sched left join thread using(utid) limit 10';
526*6dbdd20aSAndroid Build Coastguard Worker    return m(Omnibox, {
527*6dbdd20aSAndroid Build Coastguard Worker      value: AppImpl.instance.omnibox.text,
528*6dbdd20aSAndroid Build Coastguard Worker      placeholder: ph,
529*6dbdd20aSAndroid Build Coastguard Worker      inputRef: OMNIBOX_INPUT_REF,
530*6dbdd20aSAndroid Build Coastguard Worker      extraClasses: 'query-mode',
531*6dbdd20aSAndroid Build Coastguard Worker
532*6dbdd20aSAndroid Build Coastguard Worker      onInput: (value) => {
533*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.omnibox.setText(value);
534*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
535*6dbdd20aSAndroid Build Coastguard Worker      },
536*6dbdd20aSAndroid Build Coastguard Worker      onSubmit: (query, alt) => {
537*6dbdd20aSAndroid Build Coastguard Worker        const config = {
538*6dbdd20aSAndroid Build Coastguard Worker          query: undoCommonChatAppReplacements(query),
539*6dbdd20aSAndroid Build Coastguard Worker          title: alt ? 'Pinned query' : 'Omnibox query',
540*6dbdd20aSAndroid Build Coastguard Worker        };
541*6dbdd20aSAndroid Build Coastguard Worker        const tag = alt ? undefined : 'omnibox_query';
542*6dbdd20aSAndroid Build Coastguard Worker        const trace = AppImpl.instance.trace;
543*6dbdd20aSAndroid Build Coastguard Worker        if (trace === undefined) return; // No trace loaded
544*6dbdd20aSAndroid Build Coastguard Worker        addQueryResultsTab(trace, config, tag);
545*6dbdd20aSAndroid Build Coastguard Worker      },
546*6dbdd20aSAndroid Build Coastguard Worker      onClose: () => {
547*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.omnibox.setText('');
548*6dbdd20aSAndroid Build Coastguard Worker        if (this.omniboxInputEl) {
549*6dbdd20aSAndroid Build Coastguard Worker          this.omniboxInputEl.blur();
550*6dbdd20aSAndroid Build Coastguard Worker        }
551*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.omnibox.reset();
552*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw();
553*6dbdd20aSAndroid Build Coastguard Worker      },
554*6dbdd20aSAndroid Build Coastguard Worker      onGoBack: () => {
555*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.omnibox.reset();
556*6dbdd20aSAndroid Build Coastguard Worker      },
557*6dbdd20aSAndroid Build Coastguard Worker    });
558*6dbdd20aSAndroid Build Coastguard Worker  }
559*6dbdd20aSAndroid Build Coastguard Worker
560*6dbdd20aSAndroid Build Coastguard Worker  renderSearchOmnibox(): m.Children {
561*6dbdd20aSAndroid Build Coastguard Worker    return m(Omnibox, {
562*6dbdd20aSAndroid Build Coastguard Worker      value: AppImpl.instance.omnibox.text,
563*6dbdd20aSAndroid Build Coastguard Worker      placeholder: "Search or type '>' for commands or ':' for SQL mode",
564*6dbdd20aSAndroid Build Coastguard Worker      inputRef: OMNIBOX_INPUT_REF,
565*6dbdd20aSAndroid Build Coastguard Worker      onInput: (value, _prev) => {
566*6dbdd20aSAndroid Build Coastguard Worker        if (value === '>') {
567*6dbdd20aSAndroid Build Coastguard Worker          AppImpl.instance.omnibox.setMode(OmniboxMode.Command);
568*6dbdd20aSAndroid Build Coastguard Worker          return;
569*6dbdd20aSAndroid Build Coastguard Worker        } else if (value === ':') {
570*6dbdd20aSAndroid Build Coastguard Worker          AppImpl.instance.omnibox.setMode(OmniboxMode.Query);
571*6dbdd20aSAndroid Build Coastguard Worker          return;
572*6dbdd20aSAndroid Build Coastguard Worker        }
573*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.omnibox.setText(value);
574*6dbdd20aSAndroid Build Coastguard Worker        if (this.trace === undefined) return; // No trace loaded.
575*6dbdd20aSAndroid Build Coastguard Worker        if (value.length >= 4) {
576*6dbdd20aSAndroid Build Coastguard Worker          this.trace.search.search(value);
577*6dbdd20aSAndroid Build Coastguard Worker        } else {
578*6dbdd20aSAndroid Build Coastguard Worker          this.trace.search.reset();
579*6dbdd20aSAndroid Build Coastguard Worker        }
580*6dbdd20aSAndroid Build Coastguard Worker      },
581*6dbdd20aSAndroid Build Coastguard Worker      onClose: () => {
582*6dbdd20aSAndroid Build Coastguard Worker        if (this.omniboxInputEl) {
583*6dbdd20aSAndroid Build Coastguard Worker          this.omniboxInputEl.blur();
584*6dbdd20aSAndroid Build Coastguard Worker        }
585*6dbdd20aSAndroid Build Coastguard Worker      },
586*6dbdd20aSAndroid Build Coastguard Worker      onSubmit: (value, _mod, shift) => {
587*6dbdd20aSAndroid Build Coastguard Worker        if (this.trace === undefined) return; // No trace loaded.
588*6dbdd20aSAndroid Build Coastguard Worker        this.trace.search.search(value);
589*6dbdd20aSAndroid Build Coastguard Worker        if (shift) {
590*6dbdd20aSAndroid Build Coastguard Worker          this.trace.search.stepBackwards();
591*6dbdd20aSAndroid Build Coastguard Worker        } else {
592*6dbdd20aSAndroid Build Coastguard Worker          this.trace.search.stepForward();
593*6dbdd20aSAndroid Build Coastguard Worker        }
594*6dbdd20aSAndroid Build Coastguard Worker        if (this.omniboxInputEl) {
595*6dbdd20aSAndroid Build Coastguard Worker          this.omniboxInputEl.blur();
596*6dbdd20aSAndroid Build Coastguard Worker        }
597*6dbdd20aSAndroid Build Coastguard Worker      },
598*6dbdd20aSAndroid Build Coastguard Worker      rightContent: this.renderStepThrough(),
599*6dbdd20aSAndroid Build Coastguard Worker    });
600*6dbdd20aSAndroid Build Coastguard Worker  }
601*6dbdd20aSAndroid Build Coastguard Worker
602*6dbdd20aSAndroid Build Coastguard Worker  private renderStepThrough() {
603*6dbdd20aSAndroid Build Coastguard Worker    const children = [];
604*6dbdd20aSAndroid Build Coastguard Worker    const results = this.trace?.search.searchResults;
605*6dbdd20aSAndroid Build Coastguard Worker    if (this.trace?.search.searchInProgress) {
606*6dbdd20aSAndroid Build Coastguard Worker      children.push(m('.current', m(Spinner)));
607*6dbdd20aSAndroid Build Coastguard Worker    } else if (results !== undefined) {
608*6dbdd20aSAndroid Build Coastguard Worker      const searchMgr = assertExists(this.trace).search;
609*6dbdd20aSAndroid Build Coastguard Worker      const index = searchMgr.resultIndex;
610*6dbdd20aSAndroid Build Coastguard Worker      const total = results.totalResults ?? 0;
611*6dbdd20aSAndroid Build Coastguard Worker      children.push(
612*6dbdd20aSAndroid Build Coastguard Worker        m('.current', `${total === 0 ? '0 / 0' : `${index + 1} / ${total}`}`),
613*6dbdd20aSAndroid Build Coastguard Worker        m(
614*6dbdd20aSAndroid Build Coastguard Worker          'button',
615*6dbdd20aSAndroid Build Coastguard Worker          {
616*6dbdd20aSAndroid Build Coastguard Worker            onclick: () => searchMgr.stepBackwards(),
617*6dbdd20aSAndroid Build Coastguard Worker          },
618*6dbdd20aSAndroid Build Coastguard Worker          m('i.material-icons.left', 'keyboard_arrow_left'),
619*6dbdd20aSAndroid Build Coastguard Worker        ),
620*6dbdd20aSAndroid Build Coastguard Worker        m(
621*6dbdd20aSAndroid Build Coastguard Worker          'button',
622*6dbdd20aSAndroid Build Coastguard Worker          {
623*6dbdd20aSAndroid Build Coastguard Worker            onclick: () => searchMgr.stepForward(),
624*6dbdd20aSAndroid Build Coastguard Worker          },
625*6dbdd20aSAndroid Build Coastguard Worker          m('i.material-icons.right', 'keyboard_arrow_right'),
626*6dbdd20aSAndroid Build Coastguard Worker        ),
627*6dbdd20aSAndroid Build Coastguard Worker      );
628*6dbdd20aSAndroid Build Coastguard Worker    }
629*6dbdd20aSAndroid Build Coastguard Worker    return m('.stepthrough', children);
630*6dbdd20aSAndroid Build Coastguard Worker  }
631*6dbdd20aSAndroid Build Coastguard Worker
632*6dbdd20aSAndroid Build Coastguard Worker  oncreate(vnode: m.VnodeDOM) {
633*6dbdd20aSAndroid Build Coastguard Worker    this.updateOmniboxInputRef(vnode.dom);
634*6dbdd20aSAndroid Build Coastguard Worker    this.maybeFocusOmnibar();
635*6dbdd20aSAndroid Build Coastguard Worker  }
636*6dbdd20aSAndroid Build Coastguard Worker
637*6dbdd20aSAndroid Build Coastguard Worker  view(): m.Children {
638*6dbdd20aSAndroid Build Coastguard Worker    const app = AppImpl.instance;
639*6dbdd20aSAndroid Build Coastguard Worker    const hotkeys: HotkeyConfig[] = [];
640*6dbdd20aSAndroid Build Coastguard Worker    for (const {id, defaultHotkey} of app.commands.commands) {
641*6dbdd20aSAndroid Build Coastguard Worker      if (defaultHotkey) {
642*6dbdd20aSAndroid Build Coastguard Worker        hotkeys.push({
643*6dbdd20aSAndroid Build Coastguard Worker          callback: () => app.commands.runCommand(id),
644*6dbdd20aSAndroid Build Coastguard Worker          hotkey: defaultHotkey,
645*6dbdd20aSAndroid Build Coastguard Worker        });
646*6dbdd20aSAndroid Build Coastguard Worker      }
647*6dbdd20aSAndroid Build Coastguard Worker    }
648*6dbdd20aSAndroid Build Coastguard Worker
649*6dbdd20aSAndroid Build Coastguard Worker    return m(
650*6dbdd20aSAndroid Build Coastguard Worker      HotkeyContext,
651*6dbdd20aSAndroid Build Coastguard Worker      {hotkeys},
652*6dbdd20aSAndroid Build Coastguard Worker      m(
653*6dbdd20aSAndroid Build Coastguard Worker        'main',
654*6dbdd20aSAndroid Build Coastguard Worker        m(Sidebar, {trace: this.trace}),
655*6dbdd20aSAndroid Build Coastguard Worker        m(Topbar, {
656*6dbdd20aSAndroid Build Coastguard Worker          omnibox: this.renderOmnibox(),
657*6dbdd20aSAndroid Build Coastguard Worker          trace: this.trace,
658*6dbdd20aSAndroid Build Coastguard Worker        }),
659*6dbdd20aSAndroid Build Coastguard Worker        app.pages.renderPageForCurrentRoute(app.trace),
660*6dbdd20aSAndroid Build Coastguard Worker        m(CookieConsent),
661*6dbdd20aSAndroid Build Coastguard Worker        maybeRenderFullscreenModalDialog(),
662*6dbdd20aSAndroid Build Coastguard Worker        app.perfDebugging.renderPerfStats(),
663*6dbdd20aSAndroid Build Coastguard Worker      ),
664*6dbdd20aSAndroid Build Coastguard Worker    );
665*6dbdd20aSAndroid Build Coastguard Worker  }
666*6dbdd20aSAndroid Build Coastguard Worker
667*6dbdd20aSAndroid Build Coastguard Worker  onupdate({dom}: m.VnodeDOM) {
668*6dbdd20aSAndroid Build Coastguard Worker    this.updateOmniboxInputRef(dom);
669*6dbdd20aSAndroid Build Coastguard Worker    this.maybeFocusOmnibar();
670*6dbdd20aSAndroid Build Coastguard Worker  }
671*6dbdd20aSAndroid Build Coastguard Worker
672*6dbdd20aSAndroid Build Coastguard Worker  onremove(_: m.VnodeDOM) {
673*6dbdd20aSAndroid Build Coastguard Worker    this.omniboxInputEl = undefined;
674*6dbdd20aSAndroid Build Coastguard Worker
675*6dbdd20aSAndroid Build Coastguard Worker    // NOTE: if this becomes ever an asyncDispose(), then the promise needs to
676*6dbdd20aSAndroid Build Coastguard Worker    // be returned to onbeforeremove, so mithril delays the removal until
677*6dbdd20aSAndroid Build Coastguard Worker    // the promise is resolved, but then also the UiMain wrapper needs to be
678*6dbdd20aSAndroid Build Coastguard Worker    // more complex to linearize the destruction of the old instane with the
679*6dbdd20aSAndroid Build Coastguard Worker    // creation of the new one, without overlaps.
680*6dbdd20aSAndroid Build Coastguard Worker    // However, we should not add disposables that issue cleanup queries on the
681*6dbdd20aSAndroid Build Coastguard Worker    // Engine. Doing so is: (1) useless: we throw away the whole wasm instance
682*6dbdd20aSAndroid Build Coastguard Worker    // on each trace load, so what's the point of deleting tables from a TP
683*6dbdd20aSAndroid Build Coastguard Worker    // instance that is going to be destroyed?; (2) harmful: we don't have
684*6dbdd20aSAndroid Build Coastguard Worker    // precise linearization with the wasm teardown, so we might end up awaiting
685*6dbdd20aSAndroid Build Coastguard Worker    // forever for the asyncDispose() because the query will never run.
686*6dbdd20aSAndroid Build Coastguard Worker    this.trash.dispose();
687*6dbdd20aSAndroid Build Coastguard Worker  }
688*6dbdd20aSAndroid Build Coastguard Worker
689*6dbdd20aSAndroid Build Coastguard Worker  private updateOmniboxInputRef(dom: Element): void {
690*6dbdd20aSAndroid Build Coastguard Worker    const el = findRef(dom, OMNIBOX_INPUT_REF);
691*6dbdd20aSAndroid Build Coastguard Worker    if (el && el instanceof HTMLInputElement) {
692*6dbdd20aSAndroid Build Coastguard Worker      this.omniboxInputEl = el;
693*6dbdd20aSAndroid Build Coastguard Worker    }
694*6dbdd20aSAndroid Build Coastguard Worker  }
695*6dbdd20aSAndroid Build Coastguard Worker
696*6dbdd20aSAndroid Build Coastguard Worker  private maybeFocusOmnibar() {
697*6dbdd20aSAndroid Build Coastguard Worker    if (AppImpl.instance.omnibox.focusOmniboxNextRender) {
698*6dbdd20aSAndroid Build Coastguard Worker      const omniboxEl = this.omniboxInputEl;
699*6dbdd20aSAndroid Build Coastguard Worker      if (omniboxEl) {
700*6dbdd20aSAndroid Build Coastguard Worker        omniboxEl.focus();
701*6dbdd20aSAndroid Build Coastguard Worker        if (AppImpl.instance.omnibox.pendingCursorPlacement === undefined) {
702*6dbdd20aSAndroid Build Coastguard Worker          omniboxEl.select();
703*6dbdd20aSAndroid Build Coastguard Worker        } else {
704*6dbdd20aSAndroid Build Coastguard Worker          omniboxEl.setSelectionRange(
705*6dbdd20aSAndroid Build Coastguard Worker            AppImpl.instance.omnibox.pendingCursorPlacement,
706*6dbdd20aSAndroid Build Coastguard Worker            AppImpl.instance.omnibox.pendingCursorPlacement,
707*6dbdd20aSAndroid Build Coastguard Worker          );
708*6dbdd20aSAndroid Build Coastguard Worker        }
709*6dbdd20aSAndroid Build Coastguard Worker      }
710*6dbdd20aSAndroid Build Coastguard Worker      AppImpl.instance.omnibox.clearFocusFlag();
711*6dbdd20aSAndroid Build Coastguard Worker    }
712*6dbdd20aSAndroid Build Coastguard Worker  }
713*6dbdd20aSAndroid Build Coastguard Worker
714*6dbdd20aSAndroid Build Coastguard Worker  private async maybeShowJsonWarning() {
715*6dbdd20aSAndroid Build Coastguard Worker    // Show warning if the trace is in JSON format.
716*6dbdd20aSAndroid Build Coastguard Worker    const isJsonTrace = this.trace?.traceInfo.traceType === 'json';
717*6dbdd20aSAndroid Build Coastguard Worker    const SHOWN_JSON_WARNING_KEY = 'shownJsonWarning';
718*6dbdd20aSAndroid Build Coastguard Worker
719*6dbdd20aSAndroid Build Coastguard Worker    if (
720*6dbdd20aSAndroid Build Coastguard Worker      !isJsonTrace ||
721*6dbdd20aSAndroid Build Coastguard Worker      window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) === 'true' ||
722*6dbdd20aSAndroid Build Coastguard Worker      AppImpl.instance.embeddedMode
723*6dbdd20aSAndroid Build Coastguard Worker    ) {
724*6dbdd20aSAndroid Build Coastguard Worker      // When in embedded mode, the host app will control which trace format
725*6dbdd20aSAndroid Build Coastguard Worker      // it passes to Perfetto, so we don't need to show this warning.
726*6dbdd20aSAndroid Build Coastguard Worker      return;
727*6dbdd20aSAndroid Build Coastguard Worker    }
728*6dbdd20aSAndroid Build Coastguard Worker
729*6dbdd20aSAndroid Build Coastguard Worker    // Save that the warning has been shown. Value is irrelevant since only
730*6dbdd20aSAndroid Build Coastguard Worker    // the presence of key is going to be checked.
731*6dbdd20aSAndroid Build Coastguard Worker    window.localStorage.setItem(SHOWN_JSON_WARNING_KEY, 'true');
732*6dbdd20aSAndroid Build Coastguard Worker
733*6dbdd20aSAndroid Build Coastguard Worker    showModal({
734*6dbdd20aSAndroid Build Coastguard Worker      title: 'Warning',
735*6dbdd20aSAndroid Build Coastguard Worker      content: m(
736*6dbdd20aSAndroid Build Coastguard Worker        'div',
737*6dbdd20aSAndroid Build Coastguard Worker        m(
738*6dbdd20aSAndroid Build Coastguard Worker          'span',
739*6dbdd20aSAndroid Build Coastguard Worker          'Perfetto UI features are limited for JSON traces. ',
740*6dbdd20aSAndroid Build Coastguard Worker          'We recommend recording ',
741*6dbdd20aSAndroid Build Coastguard Worker          m(
742*6dbdd20aSAndroid Build Coastguard Worker            'a',
743*6dbdd20aSAndroid Build Coastguard Worker            {href: 'https://perfetto.dev/docs/quickstart/chrome-tracing'},
744*6dbdd20aSAndroid Build Coastguard Worker            'proto-format traces',
745*6dbdd20aSAndroid Build Coastguard Worker          ),
746*6dbdd20aSAndroid Build Coastguard Worker          ' from Chrome.',
747*6dbdd20aSAndroid Build Coastguard Worker        ),
748*6dbdd20aSAndroid Build Coastguard Worker        m('br'),
749*6dbdd20aSAndroid Build Coastguard Worker      ),
750*6dbdd20aSAndroid Build Coastguard Worker      buttons: [],
751*6dbdd20aSAndroid Build Coastguard Worker    });
752*6dbdd20aSAndroid Build Coastguard Worker  }
753*6dbdd20aSAndroid Build Coastguard Worker}
754