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