xref: /aosp_15_r20/external/perfetto/ui/src/frontend/sidebar.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2018 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 {getCurrentChannel} from '../core/channels';
17*6dbdd20aSAndroid Build Coastguard Workerimport {TRACE_SUFFIX} from '../public/trace';
18*6dbdd20aSAndroid Build Coastguard Workerimport {
19*6dbdd20aSAndroid Build Coastguard Worker  disableMetatracingAndGetTrace,
20*6dbdd20aSAndroid Build Coastguard Worker  enableMetatracing,
21*6dbdd20aSAndroid Build Coastguard Worker  isMetatracingEnabled,
22*6dbdd20aSAndroid Build Coastguard Worker} from '../core/metatracing';
23*6dbdd20aSAndroid Build Coastguard Workerimport {Engine, EngineMode} from '../trace_processor/engine';
24*6dbdd20aSAndroid Build Coastguard Workerimport {featureFlags} from '../core/feature_flags';
25*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../core/raf_scheduler';
26*6dbdd20aSAndroid Build Coastguard Workerimport {SCM_REVISION, VERSION} from '../gen/perfetto_version';
27*6dbdd20aSAndroid Build Coastguard Workerimport {showModal} from '../widgets/modal';
28*6dbdd20aSAndroid Build Coastguard Workerimport {Animation} from './animation';
29*6dbdd20aSAndroid Build Coastguard Workerimport {downloadData, downloadUrl} from '../base/download_utils';
30*6dbdd20aSAndroid Build Coastguard Workerimport {globals} from './globals';
31*6dbdd20aSAndroid Build Coastguard Workerimport {toggleHelp} from './help_modal';
32*6dbdd20aSAndroid Build Coastguard Workerimport {shareTrace} from './trace_share_utils';
33*6dbdd20aSAndroid Build Coastguard Workerimport {
34*6dbdd20aSAndroid Build Coastguard Worker  convertTraceToJsonAndDownload,
35*6dbdd20aSAndroid Build Coastguard Worker  convertTraceToSystraceAndDownload,
36*6dbdd20aSAndroid Build Coastguard Worker} from './trace_converter';
37*6dbdd20aSAndroid Build Coastguard Workerimport {openInOldUIWithSizeCheck} from './legacy_trace_viewer';
38*6dbdd20aSAndroid Build Coastguard Workerimport {SIDEBAR_SECTIONS, SidebarSections} from '../public/sidebar';
39*6dbdd20aSAndroid Build Coastguard Workerimport {AppImpl} from '../core/app_impl';
40*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../public/trace';
41*6dbdd20aSAndroid Build Coastguard Workerimport {OptionalTraceImplAttrs, TraceImpl} from '../core/trace_impl';
42*6dbdd20aSAndroid Build Coastguard Workerimport {Command} from '../public/command';
43*6dbdd20aSAndroid Build Coastguard Workerimport {SidebarMenuItemInternal} from '../core/sidebar_manager';
44*6dbdd20aSAndroid Build Coastguard Workerimport {exists, getOrCreate} from '../base/utils';
45*6dbdd20aSAndroid Build Coastguard Workerimport {copyToClipboard} from '../base/clipboard';
46*6dbdd20aSAndroid Build Coastguard Workerimport {classNames} from '../base/classnames';
47*6dbdd20aSAndroid Build Coastguard Workerimport {formatHotkey} from '../base/hotkeys';
48*6dbdd20aSAndroid Build Coastguard Workerimport {assetSrc} from '../base/assets';
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Workerconst GITILES_URL =
51*6dbdd20aSAndroid Build Coastguard Worker  'https://android.googlesource.com/platform/external/perfetto';
52*6dbdd20aSAndroid Build Coastguard Worker
53*6dbdd20aSAndroid Build Coastguard Workerfunction getBugReportUrl(): string {
54*6dbdd20aSAndroid Build Coastguard Worker  if (globals.isInternalUser) {
55*6dbdd20aSAndroid Build Coastguard Worker    return 'https://goto.google.com/perfetto-ui-bug';
56*6dbdd20aSAndroid Build Coastguard Worker  } else {
57*6dbdd20aSAndroid Build Coastguard Worker    return 'https://github.com/google/perfetto/issues/new';
58*6dbdd20aSAndroid Build Coastguard Worker  }
59*6dbdd20aSAndroid Build Coastguard Worker}
60*6dbdd20aSAndroid Build Coastguard Worker
61*6dbdd20aSAndroid Build Coastguard Workerconst HIRING_BANNER_FLAG = featureFlags.register({
62*6dbdd20aSAndroid Build Coastguard Worker  id: 'showHiringBanner',
63*6dbdd20aSAndroid Build Coastguard Worker  name: 'Show hiring banner',
64*6dbdd20aSAndroid Build Coastguard Worker  description: 'Show the "We\'re hiring" banner link in the side bar.',
65*6dbdd20aSAndroid Build Coastguard Worker  defaultValue: false,
66*6dbdd20aSAndroid Build Coastguard Worker});
67*6dbdd20aSAndroid Build Coastguard Worker
68*6dbdd20aSAndroid Build Coastguard Workerfunction shouldShowHiringBanner(): boolean {
69*6dbdd20aSAndroid Build Coastguard Worker  return globals.isInternalUser && HIRING_BANNER_FLAG.get();
70*6dbdd20aSAndroid Build Coastguard Worker}
71*6dbdd20aSAndroid Build Coastguard Worker
72*6dbdd20aSAndroid Build Coastguard Workerasync function openCurrentTraceWithOldUI(trace: Trace): Promise<void> {
73*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent(
74*6dbdd20aSAndroid Build Coastguard Worker    'Trace Actions',
75*6dbdd20aSAndroid Build Coastguard Worker    'Open current trace in legacy UI',
76*6dbdd20aSAndroid Build Coastguard Worker  );
77*6dbdd20aSAndroid Build Coastguard Worker  const file = await trace.getTraceFile();
78*6dbdd20aSAndroid Build Coastguard Worker  await openInOldUIWithSizeCheck(file);
79*6dbdd20aSAndroid Build Coastguard Worker}
80*6dbdd20aSAndroid Build Coastguard Worker
81*6dbdd20aSAndroid Build Coastguard Workerasync function convertTraceToSystrace(trace: Trace): Promise<void> {
82*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .systrace');
83*6dbdd20aSAndroid Build Coastguard Worker  const file = await trace.getTraceFile();
84*6dbdd20aSAndroid Build Coastguard Worker  await convertTraceToSystraceAndDownload(file);
85*6dbdd20aSAndroid Build Coastguard Worker}
86*6dbdd20aSAndroid Build Coastguard Worker
87*6dbdd20aSAndroid Build Coastguard Workerasync function convertTraceToJson(trace: Trace): Promise<void> {
88*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .json');
89*6dbdd20aSAndroid Build Coastguard Worker  const file = await trace.getTraceFile();
90*6dbdd20aSAndroid Build Coastguard Worker  await convertTraceToJsonAndDownload(file);
91*6dbdd20aSAndroid Build Coastguard Worker}
92*6dbdd20aSAndroid Build Coastguard Worker
93*6dbdd20aSAndroid Build Coastguard Workerfunction downloadTrace(trace: TraceImpl) {
94*6dbdd20aSAndroid Build Coastguard Worker  if (!trace.traceInfo.downloadable) return;
95*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent('Trace Actions', 'Download trace');
96*6dbdd20aSAndroid Build Coastguard Worker
97*6dbdd20aSAndroid Build Coastguard Worker  let url = '';
98*6dbdd20aSAndroid Build Coastguard Worker  let fileName = `trace${TRACE_SUFFIX}`;
99*6dbdd20aSAndroid Build Coastguard Worker  const src = trace.traceInfo.source;
100*6dbdd20aSAndroid Build Coastguard Worker  if (src.type === 'URL') {
101*6dbdd20aSAndroid Build Coastguard Worker    url = src.url;
102*6dbdd20aSAndroid Build Coastguard Worker    fileName = url.split('/').slice(-1)[0];
103*6dbdd20aSAndroid Build Coastguard Worker  } else if (src.type === 'ARRAY_BUFFER') {
104*6dbdd20aSAndroid Build Coastguard Worker    const blob = new Blob([src.buffer], {type: 'application/octet-stream'});
105*6dbdd20aSAndroid Build Coastguard Worker    const inputFileName = window.prompt(
106*6dbdd20aSAndroid Build Coastguard Worker      'Please enter a name for your file or leave blank',
107*6dbdd20aSAndroid Build Coastguard Worker    );
108*6dbdd20aSAndroid Build Coastguard Worker    if (inputFileName) {
109*6dbdd20aSAndroid Build Coastguard Worker      fileName = `${inputFileName}.perfetto_trace.gz`;
110*6dbdd20aSAndroid Build Coastguard Worker    } else if (src.fileName) {
111*6dbdd20aSAndroid Build Coastguard Worker      fileName = src.fileName;
112*6dbdd20aSAndroid Build Coastguard Worker    }
113*6dbdd20aSAndroid Build Coastguard Worker    url = URL.createObjectURL(blob);
114*6dbdd20aSAndroid Build Coastguard Worker  } else if (src.type === 'FILE') {
115*6dbdd20aSAndroid Build Coastguard Worker    const file = src.file;
116*6dbdd20aSAndroid Build Coastguard Worker    url = URL.createObjectURL(file);
117*6dbdd20aSAndroid Build Coastguard Worker    fileName = file.name;
118*6dbdd20aSAndroid Build Coastguard Worker  } else {
119*6dbdd20aSAndroid Build Coastguard Worker    throw new Error(`Download from ${JSON.stringify(src)} is not supported`);
120*6dbdd20aSAndroid Build Coastguard Worker  }
121*6dbdd20aSAndroid Build Coastguard Worker  downloadUrl(fileName, url);
122*6dbdd20aSAndroid Build Coastguard Worker}
123*6dbdd20aSAndroid Build Coastguard Worker
124*6dbdd20aSAndroid Build Coastguard Workerfunction highPrecisionTimersAvailable(): boolean {
125*6dbdd20aSAndroid Build Coastguard Worker  // High precision timers are available either when the page is cross-origin
126*6dbdd20aSAndroid Build Coastguard Worker  // isolated or when the trace processor is a standalone binary.
127*6dbdd20aSAndroid Build Coastguard Worker  return (
128*6dbdd20aSAndroid Build Coastguard Worker    window.crossOriginIsolated ||
129*6dbdd20aSAndroid Build Coastguard Worker    AppImpl.instance.trace?.engine.mode === 'HTTP_RPC'
130*6dbdd20aSAndroid Build Coastguard Worker  );
131*6dbdd20aSAndroid Build Coastguard Worker}
132*6dbdd20aSAndroid Build Coastguard Worker
133*6dbdd20aSAndroid Build Coastguard Workerfunction recordMetatrace(engine: Engine) {
134*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent('Trace Actions', 'Record metatrace');
135*6dbdd20aSAndroid Build Coastguard Worker
136*6dbdd20aSAndroid Build Coastguard Worker  if (!highPrecisionTimersAvailable()) {
137*6dbdd20aSAndroid Build Coastguard Worker    const PROMPT = `High-precision timers are not available to WASM trace processor yet.
138*6dbdd20aSAndroid Build Coastguard Worker
139*6dbdd20aSAndroid Build Coastguard WorkerModern browsers restrict high-precision timers to cross-origin-isolated pages.
140*6dbdd20aSAndroid Build Coastguard WorkerAs Perfetto UI needs to open traces via postMessage, it can't be cross-origin
141*6dbdd20aSAndroid Build Coastguard Workerisolated until browsers ship support for
142*6dbdd20aSAndroid Build Coastguard Worker'Cross-origin-opener-policy: restrict-properties'.
143*6dbdd20aSAndroid Build Coastguard Worker
144*6dbdd20aSAndroid Build Coastguard WorkerDo you still want to record a metatrace?
145*6dbdd20aSAndroid Build Coastguard WorkerNote that events under timer precision (1ms) will dropped.
146*6dbdd20aSAndroid Build Coastguard WorkerAlternatively, connect to a trace_processor_shell --httpd instance.
147*6dbdd20aSAndroid Build Coastguard Worker`;
148*6dbdd20aSAndroid Build Coastguard Worker    showModal({
149*6dbdd20aSAndroid Build Coastguard Worker      title: `Trace processor doesn't have high-precision timers`,
150*6dbdd20aSAndroid Build Coastguard Worker      content: m('.modal-pre', PROMPT),
151*6dbdd20aSAndroid Build Coastguard Worker      buttons: [
152*6dbdd20aSAndroid Build Coastguard Worker        {
153*6dbdd20aSAndroid Build Coastguard Worker          text: 'YES, record metatrace',
154*6dbdd20aSAndroid Build Coastguard Worker          primary: true,
155*6dbdd20aSAndroid Build Coastguard Worker          action: () => {
156*6dbdd20aSAndroid Build Coastguard Worker            enableMetatracing();
157*6dbdd20aSAndroid Build Coastguard Worker            engine.enableMetatrace();
158*6dbdd20aSAndroid Build Coastguard Worker          },
159*6dbdd20aSAndroid Build Coastguard Worker        },
160*6dbdd20aSAndroid Build Coastguard Worker        {
161*6dbdd20aSAndroid Build Coastguard Worker          text: 'NO, cancel',
162*6dbdd20aSAndroid Build Coastguard Worker        },
163*6dbdd20aSAndroid Build Coastguard Worker      ],
164*6dbdd20aSAndroid Build Coastguard Worker    });
165*6dbdd20aSAndroid Build Coastguard Worker  } else {
166*6dbdd20aSAndroid Build Coastguard Worker    engine.enableMetatrace();
167*6dbdd20aSAndroid Build Coastguard Worker  }
168*6dbdd20aSAndroid Build Coastguard Worker}
169*6dbdd20aSAndroid Build Coastguard Worker
170*6dbdd20aSAndroid Build Coastguard Workerasync function toggleMetatrace(e: Engine) {
171*6dbdd20aSAndroid Build Coastguard Worker  return isMetatracingEnabled() ? finaliseMetatrace(e) : recordMetatrace(e);
172*6dbdd20aSAndroid Build Coastguard Worker}
173*6dbdd20aSAndroid Build Coastguard Worker
174*6dbdd20aSAndroid Build Coastguard Workerasync function finaliseMetatrace(engine: Engine) {
175*6dbdd20aSAndroid Build Coastguard Worker  AppImpl.instance.analytics.logEvent('Trace Actions', 'Finalise metatrace');
176*6dbdd20aSAndroid Build Coastguard Worker
177*6dbdd20aSAndroid Build Coastguard Worker  const jsEvents = disableMetatracingAndGetTrace();
178*6dbdd20aSAndroid Build Coastguard Worker
179*6dbdd20aSAndroid Build Coastguard Worker  const result = await engine.stopAndGetMetatrace();
180*6dbdd20aSAndroid Build Coastguard Worker  if (result.error.length !== 0) {
181*6dbdd20aSAndroid Build Coastguard Worker    throw new Error(`Failed to read metatrace: ${result.error}`);
182*6dbdd20aSAndroid Build Coastguard Worker  }
183*6dbdd20aSAndroid Build Coastguard Worker
184*6dbdd20aSAndroid Build Coastguard Worker  downloadData('metatrace', result.metatrace, jsEvents);
185*6dbdd20aSAndroid Build Coastguard Worker}
186*6dbdd20aSAndroid Build Coastguard Worker
187*6dbdd20aSAndroid Build Coastguard Workerclass EngineRPCWidget implements m.ClassComponent<OptionalTraceImplAttrs> {
188*6dbdd20aSAndroid Build Coastguard Worker  view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
189*6dbdd20aSAndroid Build Coastguard Worker    let cssClass = '';
190*6dbdd20aSAndroid Build Coastguard Worker    let title = 'Number of pending SQL queries';
191*6dbdd20aSAndroid Build Coastguard Worker    let label: string;
192*6dbdd20aSAndroid Build Coastguard Worker    let failed = false;
193*6dbdd20aSAndroid Build Coastguard Worker    let mode: EngineMode | undefined;
194*6dbdd20aSAndroid Build Coastguard Worker
195*6dbdd20aSAndroid Build Coastguard Worker    const engine = attrs.trace?.engine;
196*6dbdd20aSAndroid Build Coastguard Worker    if (engine !== undefined) {
197*6dbdd20aSAndroid Build Coastguard Worker      mode = engine.mode;
198*6dbdd20aSAndroid Build Coastguard Worker      if (engine.failed !== undefined) {
199*6dbdd20aSAndroid Build Coastguard Worker        cssClass += '.red';
200*6dbdd20aSAndroid Build Coastguard Worker        title = 'Query engine crashed\n' + engine.failed;
201*6dbdd20aSAndroid Build Coastguard Worker        failed = true;
202*6dbdd20aSAndroid Build Coastguard Worker      }
203*6dbdd20aSAndroid Build Coastguard Worker    }
204*6dbdd20aSAndroid Build Coastguard Worker
205*6dbdd20aSAndroid Build Coastguard Worker    // If we don't have an engine yet, guess what will be the mode that will
206*6dbdd20aSAndroid Build Coastguard Worker    // be used next time we'll create one. Even if we guess it wrong (somehow
207*6dbdd20aSAndroid Build Coastguard Worker    // trace_controller.ts takes a different decision later, e.g. because the
208*6dbdd20aSAndroid Build Coastguard Worker    // RPC server is shut down after we load the UI and cached httpRpcState)
209*6dbdd20aSAndroid Build Coastguard Worker    // this will eventually become  consistent once the engine is created.
210*6dbdd20aSAndroid Build Coastguard Worker    if (mode === undefined) {
211*6dbdd20aSAndroid Build Coastguard Worker      if (
212*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.httpRpc.httpRpcAvailable &&
213*6dbdd20aSAndroid Build Coastguard Worker        AppImpl.instance.httpRpc.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE'
214*6dbdd20aSAndroid Build Coastguard Worker      ) {
215*6dbdd20aSAndroid Build Coastguard Worker        mode = 'HTTP_RPC';
216*6dbdd20aSAndroid Build Coastguard Worker      } else {
217*6dbdd20aSAndroid Build Coastguard Worker        mode = 'WASM';
218*6dbdd20aSAndroid Build Coastguard Worker      }
219*6dbdd20aSAndroid Build Coastguard Worker    }
220*6dbdd20aSAndroid Build Coastguard Worker
221*6dbdd20aSAndroid Build Coastguard Worker    if (mode === 'HTTP_RPC') {
222*6dbdd20aSAndroid Build Coastguard Worker      cssClass += '.green';
223*6dbdd20aSAndroid Build Coastguard Worker      label = 'RPC';
224*6dbdd20aSAndroid Build Coastguard Worker      title += '\n(Query engine: native accelerator over HTTP+RPC)';
225*6dbdd20aSAndroid Build Coastguard Worker    } else {
226*6dbdd20aSAndroid Build Coastguard Worker      label = 'WSM';
227*6dbdd20aSAndroid Build Coastguard Worker      title += '\n(Query engine: built-in WASM)';
228*6dbdd20aSAndroid Build Coastguard Worker    }
229*6dbdd20aSAndroid Build Coastguard Worker
230*6dbdd20aSAndroid Build Coastguard Worker    const numReqs = attrs.trace?.engine.numRequestsPending ?? 0;
231*6dbdd20aSAndroid Build Coastguard Worker    return m(
232*6dbdd20aSAndroid Build Coastguard Worker      `.dbg-info-square${cssClass}`,
233*6dbdd20aSAndroid Build Coastguard Worker      {title},
234*6dbdd20aSAndroid Build Coastguard Worker      m('div', label),
235*6dbdd20aSAndroid Build Coastguard Worker      m('div', `${failed ? 'FAIL' : numReqs}`),
236*6dbdd20aSAndroid Build Coastguard Worker    );
237*6dbdd20aSAndroid Build Coastguard Worker  }
238*6dbdd20aSAndroid Build Coastguard Worker}
239*6dbdd20aSAndroid Build Coastguard Worker
240*6dbdd20aSAndroid Build Coastguard Workerconst ServiceWorkerWidget: m.Component = {
241*6dbdd20aSAndroid Build Coastguard Worker  view() {
242*6dbdd20aSAndroid Build Coastguard Worker    let cssClass = '';
243*6dbdd20aSAndroid Build Coastguard Worker    let title = 'Service Worker: ';
244*6dbdd20aSAndroid Build Coastguard Worker    let label = 'N/A';
245*6dbdd20aSAndroid Build Coastguard Worker    const ctl = AppImpl.instance.serviceWorkerController;
246*6dbdd20aSAndroid Build Coastguard Worker    if (!('serviceWorker' in navigator)) {
247*6dbdd20aSAndroid Build Coastguard Worker      label = 'N/A';
248*6dbdd20aSAndroid Build Coastguard Worker      title += 'not supported by the browser (requires HTTPS)';
249*6dbdd20aSAndroid Build Coastguard Worker    } else if (ctl.bypassed) {
250*6dbdd20aSAndroid Build Coastguard Worker      label = 'OFF';
251*6dbdd20aSAndroid Build Coastguard Worker      cssClass = '.red';
252*6dbdd20aSAndroid Build Coastguard Worker      title += 'Bypassed, using live network. Double-click to re-enable';
253*6dbdd20aSAndroid Build Coastguard Worker    } else if (ctl.installing) {
254*6dbdd20aSAndroid Build Coastguard Worker      label = 'UPD';
255*6dbdd20aSAndroid Build Coastguard Worker      cssClass = '.amber';
256*6dbdd20aSAndroid Build Coastguard Worker      title += 'Installing / updating ...';
257*6dbdd20aSAndroid Build Coastguard Worker    } else if (!navigator.serviceWorker.controller) {
258*6dbdd20aSAndroid Build Coastguard Worker      label = 'N/A';
259*6dbdd20aSAndroid Build Coastguard Worker      title += 'Not available, using network';
260*6dbdd20aSAndroid Build Coastguard Worker    } else {
261*6dbdd20aSAndroid Build Coastguard Worker      label = 'ON';
262*6dbdd20aSAndroid Build Coastguard Worker      cssClass = '.green';
263*6dbdd20aSAndroid Build Coastguard Worker      title += 'Serving from cache. Ready for offline use';
264*6dbdd20aSAndroid Build Coastguard Worker    }
265*6dbdd20aSAndroid Build Coastguard Worker
266*6dbdd20aSAndroid Build Coastguard Worker    const toggle = async () => {
267*6dbdd20aSAndroid Build Coastguard Worker      if (ctl.bypassed) {
268*6dbdd20aSAndroid Build Coastguard Worker        ctl.setBypass(false);
269*6dbdd20aSAndroid Build Coastguard Worker        return;
270*6dbdd20aSAndroid Build Coastguard Worker      }
271*6dbdd20aSAndroid Build Coastguard Worker      showModal({
272*6dbdd20aSAndroid Build Coastguard Worker        title: 'Disable service worker?',
273*6dbdd20aSAndroid Build Coastguard Worker        content: m(
274*6dbdd20aSAndroid Build Coastguard Worker          'div',
275*6dbdd20aSAndroid Build Coastguard Worker          m(
276*6dbdd20aSAndroid Build Coastguard Worker            'p',
277*6dbdd20aSAndroid Build Coastguard Worker            `If you continue the service worker will be disabled until
278*6dbdd20aSAndroid Build Coastguard Worker                      manually re-enabled.`,
279*6dbdd20aSAndroid Build Coastguard Worker          ),
280*6dbdd20aSAndroid Build Coastguard Worker          m(
281*6dbdd20aSAndroid Build Coastguard Worker            'p',
282*6dbdd20aSAndroid Build Coastguard Worker            `All future requests will be served from the network and the
283*6dbdd20aSAndroid Build Coastguard Worker                    UI won't be available offline.`,
284*6dbdd20aSAndroid Build Coastguard Worker          ),
285*6dbdd20aSAndroid Build Coastguard Worker          m(
286*6dbdd20aSAndroid Build Coastguard Worker            'p',
287*6dbdd20aSAndroid Build Coastguard Worker            `You should do this only if you are debugging the UI
288*6dbdd20aSAndroid Build Coastguard Worker                    or if you are experiencing caching-related problems.`,
289*6dbdd20aSAndroid Build Coastguard Worker          ),
290*6dbdd20aSAndroid Build Coastguard Worker          m(
291*6dbdd20aSAndroid Build Coastguard Worker            'p',
292*6dbdd20aSAndroid Build Coastguard Worker            `Disabling will cause a refresh of the UI, the current state
293*6dbdd20aSAndroid Build Coastguard Worker                    will be lost.`,
294*6dbdd20aSAndroid Build Coastguard Worker          ),
295*6dbdd20aSAndroid Build Coastguard Worker        ),
296*6dbdd20aSAndroid Build Coastguard Worker        buttons: [
297*6dbdd20aSAndroid Build Coastguard Worker          {
298*6dbdd20aSAndroid Build Coastguard Worker            text: 'Disable and reload',
299*6dbdd20aSAndroid Build Coastguard Worker            primary: true,
300*6dbdd20aSAndroid Build Coastguard Worker            action: () => ctl.setBypass(true).then(() => location.reload()),
301*6dbdd20aSAndroid Build Coastguard Worker          },
302*6dbdd20aSAndroid Build Coastguard Worker          {text: 'Cancel'},
303*6dbdd20aSAndroid Build Coastguard Worker        ],
304*6dbdd20aSAndroid Build Coastguard Worker      });
305*6dbdd20aSAndroid Build Coastguard Worker    };
306*6dbdd20aSAndroid Build Coastguard Worker
307*6dbdd20aSAndroid Build Coastguard Worker    return m(
308*6dbdd20aSAndroid Build Coastguard Worker      `.dbg-info-square${cssClass}`,
309*6dbdd20aSAndroid Build Coastguard Worker      {title, ondblclick: toggle},
310*6dbdd20aSAndroid Build Coastguard Worker      m('div', 'SW'),
311*6dbdd20aSAndroid Build Coastguard Worker      m('div', label),
312*6dbdd20aSAndroid Build Coastguard Worker    );
313*6dbdd20aSAndroid Build Coastguard Worker  },
314*6dbdd20aSAndroid Build Coastguard Worker};
315*6dbdd20aSAndroid Build Coastguard Worker
316*6dbdd20aSAndroid Build Coastguard Workerclass SidebarFooter implements m.ClassComponent<OptionalTraceImplAttrs> {
317*6dbdd20aSAndroid Build Coastguard Worker  view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
318*6dbdd20aSAndroid Build Coastguard Worker    return m(
319*6dbdd20aSAndroid Build Coastguard Worker      '.sidebar-footer',
320*6dbdd20aSAndroid Build Coastguard Worker      m(EngineRPCWidget, attrs),
321*6dbdd20aSAndroid Build Coastguard Worker      m(ServiceWorkerWidget),
322*6dbdd20aSAndroid Build Coastguard Worker      m(
323*6dbdd20aSAndroid Build Coastguard Worker        '.version',
324*6dbdd20aSAndroid Build Coastguard Worker        m(
325*6dbdd20aSAndroid Build Coastguard Worker          'a',
326*6dbdd20aSAndroid Build Coastguard Worker          {
327*6dbdd20aSAndroid Build Coastguard Worker            href: `${GITILES_URL}/+/${SCM_REVISION}/ui`,
328*6dbdd20aSAndroid Build Coastguard Worker            title: `Channel: ${getCurrentChannel()}`,
329*6dbdd20aSAndroid Build Coastguard Worker            target: '_blank',
330*6dbdd20aSAndroid Build Coastguard Worker          },
331*6dbdd20aSAndroid Build Coastguard Worker          VERSION,
332*6dbdd20aSAndroid Build Coastguard Worker        ),
333*6dbdd20aSAndroid Build Coastguard Worker      ),
334*6dbdd20aSAndroid Build Coastguard Worker    );
335*6dbdd20aSAndroid Build Coastguard Worker  }
336*6dbdd20aSAndroid Build Coastguard Worker}
337*6dbdd20aSAndroid Build Coastguard Worker
338*6dbdd20aSAndroid Build Coastguard Workerclass HiringBanner implements m.ClassComponent {
339*6dbdd20aSAndroid Build Coastguard Worker  view() {
340*6dbdd20aSAndroid Build Coastguard Worker    return m(
341*6dbdd20aSAndroid Build Coastguard Worker      '.hiring-banner',
342*6dbdd20aSAndroid Build Coastguard Worker      m(
343*6dbdd20aSAndroid Build Coastguard Worker        'a',
344*6dbdd20aSAndroid Build Coastguard Worker        {
345*6dbdd20aSAndroid Build Coastguard Worker          href: 'http://go/perfetto-open-roles',
346*6dbdd20aSAndroid Build Coastguard Worker          target: '_blank',
347*6dbdd20aSAndroid Build Coastguard Worker        },
348*6dbdd20aSAndroid Build Coastguard Worker        "We're hiring!",
349*6dbdd20aSAndroid Build Coastguard Worker      ),
350*6dbdd20aSAndroid Build Coastguard Worker    );
351*6dbdd20aSAndroid Build Coastguard Worker  }
352*6dbdd20aSAndroid Build Coastguard Worker}
353*6dbdd20aSAndroid Build Coastguard Worker
354*6dbdd20aSAndroid Build Coastguard Workerexport class Sidebar implements m.ClassComponent<OptionalTraceImplAttrs> {
355*6dbdd20aSAndroid Build Coastguard Worker  private _redrawWhileAnimating = new Animation(() =>
356*6dbdd20aSAndroid Build Coastguard Worker    raf.scheduleFullRedraw('force'),
357*6dbdd20aSAndroid Build Coastguard Worker  );
358*6dbdd20aSAndroid Build Coastguard Worker  private _asyncJobPending = new Set<string>();
359*6dbdd20aSAndroid Build Coastguard Worker  private _sectionExpanded = new Map<string, boolean>();
360*6dbdd20aSAndroid Build Coastguard Worker
361*6dbdd20aSAndroid Build Coastguard Worker  constructor() {
362*6dbdd20aSAndroid Build Coastguard Worker    registerMenuItems();
363*6dbdd20aSAndroid Build Coastguard Worker  }
364*6dbdd20aSAndroid Build Coastguard Worker
365*6dbdd20aSAndroid Build Coastguard Worker  view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
366*6dbdd20aSAndroid Build Coastguard Worker    const sidebar = AppImpl.instance.sidebar;
367*6dbdd20aSAndroid Build Coastguard Worker    if (!sidebar.enabled) return null;
368*6dbdd20aSAndroid Build Coastguard Worker    return m(
369*6dbdd20aSAndroid Build Coastguard Worker      'nav.sidebar',
370*6dbdd20aSAndroid Build Coastguard Worker      {
371*6dbdd20aSAndroid Build Coastguard Worker        class: sidebar.visible ? 'show-sidebar' : 'hide-sidebar',
372*6dbdd20aSAndroid Build Coastguard Worker        // 150 here matches --sidebar-timing in the css.
373*6dbdd20aSAndroid Build Coastguard Worker        // TODO(hjd): Should link to the CSS variable.
374*6dbdd20aSAndroid Build Coastguard Worker        ontransitionstart: (e: TransitionEvent) => {
375*6dbdd20aSAndroid Build Coastguard Worker          if (e.target !== e.currentTarget) return;
376*6dbdd20aSAndroid Build Coastguard Worker          this._redrawWhileAnimating.start(150);
377*6dbdd20aSAndroid Build Coastguard Worker        },
378*6dbdd20aSAndroid Build Coastguard Worker        ontransitionend: (e: TransitionEvent) => {
379*6dbdd20aSAndroid Build Coastguard Worker          if (e.target !== e.currentTarget) return;
380*6dbdd20aSAndroid Build Coastguard Worker          this._redrawWhileAnimating.stop();
381*6dbdd20aSAndroid Build Coastguard Worker        },
382*6dbdd20aSAndroid Build Coastguard Worker      },
383*6dbdd20aSAndroid Build Coastguard Worker      shouldShowHiringBanner() ? m(HiringBanner) : null,
384*6dbdd20aSAndroid Build Coastguard Worker      m(
385*6dbdd20aSAndroid Build Coastguard Worker        `header.${getCurrentChannel()}`,
386*6dbdd20aSAndroid Build Coastguard Worker        m(`img[src=${assetSrc('assets/brand.png')}].brand`),
387*6dbdd20aSAndroid Build Coastguard Worker        m(
388*6dbdd20aSAndroid Build Coastguard Worker          'button.sidebar-button',
389*6dbdd20aSAndroid Build Coastguard Worker          {
390*6dbdd20aSAndroid Build Coastguard Worker            onclick: () => sidebar.toggleVisibility(),
391*6dbdd20aSAndroid Build Coastguard Worker          },
392*6dbdd20aSAndroid Build Coastguard Worker          m(
393*6dbdd20aSAndroid Build Coastguard Worker            'i.material-icons',
394*6dbdd20aSAndroid Build Coastguard Worker            {
395*6dbdd20aSAndroid Build Coastguard Worker              title: sidebar.visible ? 'Hide menu' : 'Show menu',
396*6dbdd20aSAndroid Build Coastguard Worker            },
397*6dbdd20aSAndroid Build Coastguard Worker            'menu',
398*6dbdd20aSAndroid Build Coastguard Worker          ),
399*6dbdd20aSAndroid Build Coastguard Worker        ),
400*6dbdd20aSAndroid Build Coastguard Worker      ),
401*6dbdd20aSAndroid Build Coastguard Worker      m(
402*6dbdd20aSAndroid Build Coastguard Worker        '.sidebar-scroll',
403*6dbdd20aSAndroid Build Coastguard Worker        m(
404*6dbdd20aSAndroid Build Coastguard Worker          '.sidebar-scroll-container',
405*6dbdd20aSAndroid Build Coastguard Worker          ...(Object.keys(SIDEBAR_SECTIONS) as SidebarSections[]).map((s) =>
406*6dbdd20aSAndroid Build Coastguard Worker            this.renderSection(s),
407*6dbdd20aSAndroid Build Coastguard Worker          ),
408*6dbdd20aSAndroid Build Coastguard Worker          m(SidebarFooter, attrs),
409*6dbdd20aSAndroid Build Coastguard Worker        ),
410*6dbdd20aSAndroid Build Coastguard Worker      ),
411*6dbdd20aSAndroid Build Coastguard Worker    );
412*6dbdd20aSAndroid Build Coastguard Worker  }
413*6dbdd20aSAndroid Build Coastguard Worker
414*6dbdd20aSAndroid Build Coastguard Worker  private renderSection(sectionId: SidebarSections) {
415*6dbdd20aSAndroid Build Coastguard Worker    const section = SIDEBAR_SECTIONS[sectionId];
416*6dbdd20aSAndroid Build Coastguard Worker    const menuItems = AppImpl.instance.sidebar.menuItems
417*6dbdd20aSAndroid Build Coastguard Worker      .valuesAsArray()
418*6dbdd20aSAndroid Build Coastguard Worker      .filter((item) => item.section === sectionId)
419*6dbdd20aSAndroid Build Coastguard Worker      .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
420*6dbdd20aSAndroid Build Coastguard Worker      .map((item) => this.renderItem(item));
421*6dbdd20aSAndroid Build Coastguard Worker
422*6dbdd20aSAndroid Build Coastguard Worker    // Don't render empty sections.
423*6dbdd20aSAndroid Build Coastguard Worker    if (menuItems.length === 0) return undefined;
424*6dbdd20aSAndroid Build Coastguard Worker
425*6dbdd20aSAndroid Build Coastguard Worker    const expanded = getOrCreate(this._sectionExpanded, sectionId, () => true);
426*6dbdd20aSAndroid Build Coastguard Worker    return m(
427*6dbdd20aSAndroid Build Coastguard Worker      `section${expanded ? '.expanded' : ''}`,
428*6dbdd20aSAndroid Build Coastguard Worker      m(
429*6dbdd20aSAndroid Build Coastguard Worker        '.section-header',
430*6dbdd20aSAndroid Build Coastguard Worker        {
431*6dbdd20aSAndroid Build Coastguard Worker          onclick: () => {
432*6dbdd20aSAndroid Build Coastguard Worker            this._sectionExpanded.set(sectionId, !expanded);
433*6dbdd20aSAndroid Build Coastguard Worker            raf.scheduleFullRedraw();
434*6dbdd20aSAndroid Build Coastguard Worker          },
435*6dbdd20aSAndroid Build Coastguard Worker        },
436*6dbdd20aSAndroid Build Coastguard Worker        m('h1', {title: section.title}, section.title),
437*6dbdd20aSAndroid Build Coastguard Worker        m('h2', section.summary),
438*6dbdd20aSAndroid Build Coastguard Worker      ),
439*6dbdd20aSAndroid Build Coastguard Worker      m('.section-content', m('ul', menuItems)),
440*6dbdd20aSAndroid Build Coastguard Worker    );
441*6dbdd20aSAndroid Build Coastguard Worker  }
442*6dbdd20aSAndroid Build Coastguard Worker
443*6dbdd20aSAndroid Build Coastguard Worker  private renderItem(item: SidebarMenuItemInternal): m.Child {
444*6dbdd20aSAndroid Build Coastguard Worker    let href = '#';
445*6dbdd20aSAndroid Build Coastguard Worker    let disabled = false;
446*6dbdd20aSAndroid Build Coastguard Worker    let target = null;
447*6dbdd20aSAndroid Build Coastguard Worker    let command: Command | undefined = undefined;
448*6dbdd20aSAndroid Build Coastguard Worker    let tooltip = valueOrCallback(item.tooltip);
449*6dbdd20aSAndroid Build Coastguard Worker    let onclick: (() => unknown | Promise<unknown>) | undefined = undefined;
450*6dbdd20aSAndroid Build Coastguard Worker    const commandId = 'commandId' in item ? item.commandId : undefined;
451*6dbdd20aSAndroid Build Coastguard Worker    const action = 'action' in item ? item.action : undefined;
452*6dbdd20aSAndroid Build Coastguard Worker    let text = valueOrCallback(item.text);
453*6dbdd20aSAndroid Build Coastguard Worker    const disabReason: boolean | string | undefined = valueOrCallback(
454*6dbdd20aSAndroid Build Coastguard Worker      item.disabled,
455*6dbdd20aSAndroid Build Coastguard Worker    );
456*6dbdd20aSAndroid Build Coastguard Worker
457*6dbdd20aSAndroid Build Coastguard Worker    if (disabReason === true || typeof disabReason === 'string') {
458*6dbdd20aSAndroid Build Coastguard Worker      disabled = true;
459*6dbdd20aSAndroid Build Coastguard Worker      onclick = () => typeof disabReason === 'string' && alert(disabReason);
460*6dbdd20aSAndroid Build Coastguard Worker    } else if (action !== undefined) {
461*6dbdd20aSAndroid Build Coastguard Worker      onclick = action;
462*6dbdd20aSAndroid Build Coastguard Worker    } else if (commandId !== undefined) {
463*6dbdd20aSAndroid Build Coastguard Worker      const cmdMgr = AppImpl.instance.commands;
464*6dbdd20aSAndroid Build Coastguard Worker      command = cmdMgr.hasCommand(commandId ?? '')
465*6dbdd20aSAndroid Build Coastguard Worker        ? cmdMgr.getCommand(commandId)
466*6dbdd20aSAndroid Build Coastguard Worker        : undefined;
467*6dbdd20aSAndroid Build Coastguard Worker      if (command === undefined) {
468*6dbdd20aSAndroid Build Coastguard Worker        disabled = true;
469*6dbdd20aSAndroid Build Coastguard Worker      } else {
470*6dbdd20aSAndroid Build Coastguard Worker        text = text !== undefined ? text : command.name;
471*6dbdd20aSAndroid Build Coastguard Worker        if (command.defaultHotkey !== undefined) {
472*6dbdd20aSAndroid Build Coastguard Worker          tooltip =
473*6dbdd20aSAndroid Build Coastguard Worker            `${tooltip ?? command.name}` +
474*6dbdd20aSAndroid Build Coastguard Worker            ` [${formatHotkey(command.defaultHotkey)}]`;
475*6dbdd20aSAndroid Build Coastguard Worker        }
476*6dbdd20aSAndroid Build Coastguard Worker        onclick = () => cmdMgr.runCommand(commandId);
477*6dbdd20aSAndroid Build Coastguard Worker      }
478*6dbdd20aSAndroid Build Coastguard Worker    }
479*6dbdd20aSAndroid Build Coastguard Worker
480*6dbdd20aSAndroid Build Coastguard Worker    // This is not an else if because in some rare cases the user might want
481*6dbdd20aSAndroid Build Coastguard Worker    // to have both an href and onclick, with different behaviors. The only case
482*6dbdd20aSAndroid Build Coastguard Worker    // today is the trace name / URL, where we want the URL in the href to
483*6dbdd20aSAndroid Build Coastguard Worker    // support right-click -> copy URL, but the onclick does copyToClipboard().
484*6dbdd20aSAndroid Build Coastguard Worker    if ('href' in item && item.href !== undefined) {
485*6dbdd20aSAndroid Build Coastguard Worker      href = item.href;
486*6dbdd20aSAndroid Build Coastguard Worker      target = href.startsWith('#') ? null : '_blank';
487*6dbdd20aSAndroid Build Coastguard Worker    }
488*6dbdd20aSAndroid Build Coastguard Worker    return m(
489*6dbdd20aSAndroid Build Coastguard Worker      'li',
490*6dbdd20aSAndroid Build Coastguard Worker      m(
491*6dbdd20aSAndroid Build Coastguard Worker        'a',
492*6dbdd20aSAndroid Build Coastguard Worker        {
493*6dbdd20aSAndroid Build Coastguard Worker          className: classNames(
494*6dbdd20aSAndroid Build Coastguard Worker            valueOrCallback(item.cssClass),
495*6dbdd20aSAndroid Build Coastguard Worker            this._asyncJobPending.has(item.id) && 'pending',
496*6dbdd20aSAndroid Build Coastguard Worker          ),
497*6dbdd20aSAndroid Build Coastguard Worker          onclick: onclick && this.wrapClickHandler(item.id, onclick),
498*6dbdd20aSAndroid Build Coastguard Worker          href,
499*6dbdd20aSAndroid Build Coastguard Worker          target,
500*6dbdd20aSAndroid Build Coastguard Worker          disabled,
501*6dbdd20aSAndroid Build Coastguard Worker          title: tooltip,
502*6dbdd20aSAndroid Build Coastguard Worker        },
503*6dbdd20aSAndroid Build Coastguard Worker        exists(item.icon) && m('i.material-icons', valueOrCallback(item.icon)),
504*6dbdd20aSAndroid Build Coastguard Worker        text,
505*6dbdd20aSAndroid Build Coastguard Worker      ),
506*6dbdd20aSAndroid Build Coastguard Worker    );
507*6dbdd20aSAndroid Build Coastguard Worker  }
508*6dbdd20aSAndroid Build Coastguard Worker
509*6dbdd20aSAndroid Build Coastguard Worker  // Creates the onClick handlers for the items which provided a function in the
510*6dbdd20aSAndroid Build Coastguard Worker  // `action` member. The function can be either sync or async.
511*6dbdd20aSAndroid Build Coastguard Worker  // What we want to achieve here is the following:
512*6dbdd20aSAndroid Build Coastguard Worker  // - If the action is async (returns a Promise), we want to render a spinner,
513*6dbdd20aSAndroid Build Coastguard Worker  //   next to the menu item, until the promise is resolved.
514*6dbdd20aSAndroid Build Coastguard Worker  // - [Minor] we want to call e.preventDefault() to override the behaviour of
515*6dbdd20aSAndroid Build Coastguard Worker  //   the <a href='#'> which gets rendered for accessibility reasons.
516*6dbdd20aSAndroid Build Coastguard Worker  private wrapClickHandler(itemId: string, itemAction: Function) {
517*6dbdd20aSAndroid Build Coastguard Worker    return (e: Event) => {
518*6dbdd20aSAndroid Build Coastguard Worker      e.preventDefault(); // Make the <a href="#"> a no-op.
519*6dbdd20aSAndroid Build Coastguard Worker      const res = itemAction();
520*6dbdd20aSAndroid Build Coastguard Worker      if (!(res instanceof Promise)) return;
521*6dbdd20aSAndroid Build Coastguard Worker      if (this._asyncJobPending.has(itemId)) {
522*6dbdd20aSAndroid Build Coastguard Worker        return; // Don't queue up another action if not yet finished.
523*6dbdd20aSAndroid Build Coastguard Worker      }
524*6dbdd20aSAndroid Build Coastguard Worker      this._asyncJobPending.add(itemId);
525*6dbdd20aSAndroid Build Coastguard Worker      raf.scheduleFullRedraw();
526*6dbdd20aSAndroid Build Coastguard Worker      res.finally(() => {
527*6dbdd20aSAndroid Build Coastguard Worker        this._asyncJobPending.delete(itemId);
528*6dbdd20aSAndroid Build Coastguard Worker        raf.scheduleFullRedraw('force');
529*6dbdd20aSAndroid Build Coastguard Worker      });
530*6dbdd20aSAndroid Build Coastguard Worker    };
531*6dbdd20aSAndroid Build Coastguard Worker  }
532*6dbdd20aSAndroid Build Coastguard Worker}
533*6dbdd20aSAndroid Build Coastguard Worker
534*6dbdd20aSAndroid Build Coastguard Worker// TODO(primiano): The registrations below should be moved to dedicated
535*6dbdd20aSAndroid Build Coastguard Worker// plugins (most of this really belongs to core_plugins/commads/index.ts).
536*6dbdd20aSAndroid Build Coastguard Worker// For now i'm keeping everything here as splitting these require moving some
537*6dbdd20aSAndroid Build Coastguard Worker// functions like share_trace() out of core, splitting out permalink, etc.
538*6dbdd20aSAndroid Build Coastguard Worker
539*6dbdd20aSAndroid Build Coastguard Workerlet globalItemsRegistered = false;
540*6dbdd20aSAndroid Build Coastguard Workerconst traceItemsRegistered = new WeakSet<TraceImpl>();
541*6dbdd20aSAndroid Build Coastguard Worker
542*6dbdd20aSAndroid Build Coastguard Workerfunction registerMenuItems() {
543*6dbdd20aSAndroid Build Coastguard Worker  if (!globalItemsRegistered) {
544*6dbdd20aSAndroid Build Coastguard Worker    globalItemsRegistered = true;
545*6dbdd20aSAndroid Build Coastguard Worker    registerGlobalSidebarEntries();
546*6dbdd20aSAndroid Build Coastguard Worker  }
547*6dbdd20aSAndroid Build Coastguard Worker  const trace = AppImpl.instance.trace;
548*6dbdd20aSAndroid Build Coastguard Worker  if (trace !== undefined && !traceItemsRegistered.has(trace)) {
549*6dbdd20aSAndroid Build Coastguard Worker    traceItemsRegistered.add(trace);
550*6dbdd20aSAndroid Build Coastguard Worker    registerTraceMenuItems(trace);
551*6dbdd20aSAndroid Build Coastguard Worker  }
552*6dbdd20aSAndroid Build Coastguard Worker}
553*6dbdd20aSAndroid Build Coastguard Worker
554*6dbdd20aSAndroid Build Coastguard Workerfunction registerGlobalSidebarEntries() {
555*6dbdd20aSAndroid Build Coastguard Worker  const app = AppImpl.instance;
556*6dbdd20aSAndroid Build Coastguard Worker  // TODO(primiano): The Open file / Open with legacy entries are registered by
557*6dbdd20aSAndroid Build Coastguard Worker  // the 'perfetto.CoreCommands' plugins. Make things consistent.
558*6dbdd20aSAndroid Build Coastguard Worker  app.sidebar.addMenuItem({
559*6dbdd20aSAndroid Build Coastguard Worker    section: 'support',
560*6dbdd20aSAndroid Build Coastguard Worker    text: 'Keyboard shortcuts',
561*6dbdd20aSAndroid Build Coastguard Worker    action: toggleHelp,
562*6dbdd20aSAndroid Build Coastguard Worker    icon: 'help',
563*6dbdd20aSAndroid Build Coastguard Worker  });
564*6dbdd20aSAndroid Build Coastguard Worker  app.sidebar.addMenuItem({
565*6dbdd20aSAndroid Build Coastguard Worker    section: 'support',
566*6dbdd20aSAndroid Build Coastguard Worker    text: 'Documentation',
567*6dbdd20aSAndroid Build Coastguard Worker    href: 'https://perfetto.dev/docs',
568*6dbdd20aSAndroid Build Coastguard Worker    icon: 'find_in_page',
569*6dbdd20aSAndroid Build Coastguard Worker  });
570*6dbdd20aSAndroid Build Coastguard Worker  app.sidebar.addMenuItem({
571*6dbdd20aSAndroid Build Coastguard Worker    section: 'support',
572*6dbdd20aSAndroid Build Coastguard Worker    sortOrder: 4,
573*6dbdd20aSAndroid Build Coastguard Worker    text: 'Report a bug',
574*6dbdd20aSAndroid Build Coastguard Worker    href: getBugReportUrl(),
575*6dbdd20aSAndroid Build Coastguard Worker    icon: 'bug_report',
576*6dbdd20aSAndroid Build Coastguard Worker  });
577*6dbdd20aSAndroid Build Coastguard Worker}
578*6dbdd20aSAndroid Build Coastguard Worker
579*6dbdd20aSAndroid Build Coastguard Workerfunction registerTraceMenuItems(trace: TraceImpl) {
580*6dbdd20aSAndroid Build Coastguard Worker  const downloadDisabled = trace.traceInfo.downloadable
581*6dbdd20aSAndroid Build Coastguard Worker    ? false
582*6dbdd20aSAndroid Build Coastguard Worker    : 'Cannot download external trace';
583*6dbdd20aSAndroid Build Coastguard Worker
584*6dbdd20aSAndroid Build Coastguard Worker  const traceTitle = trace?.traceInfo.traceTitle;
585*6dbdd20aSAndroid Build Coastguard Worker  traceTitle &&
586*6dbdd20aSAndroid Build Coastguard Worker    trace.sidebar.addMenuItem({
587*6dbdd20aSAndroid Build Coastguard Worker      section: 'current_trace',
588*6dbdd20aSAndroid Build Coastguard Worker      text: traceTitle,
589*6dbdd20aSAndroid Build Coastguard Worker      href: trace.traceInfo.traceUrl,
590*6dbdd20aSAndroid Build Coastguard Worker      action: () => copyToClipboard(trace.traceInfo.traceUrl),
591*6dbdd20aSAndroid Build Coastguard Worker      tooltip: 'Click to copy the URL',
592*6dbdd20aSAndroid Build Coastguard Worker      cssClass: 'trace-file-name',
593*6dbdd20aSAndroid Build Coastguard Worker    });
594*6dbdd20aSAndroid Build Coastguard Worker  trace.sidebar.addMenuItem({
595*6dbdd20aSAndroid Build Coastguard Worker    section: 'current_trace',
596*6dbdd20aSAndroid Build Coastguard Worker    text: 'Show timeline',
597*6dbdd20aSAndroid Build Coastguard Worker    href: '#!/viewer',
598*6dbdd20aSAndroid Build Coastguard Worker    icon: 'line_style',
599*6dbdd20aSAndroid Build Coastguard Worker  });
600*6dbdd20aSAndroid Build Coastguard Worker  globals.isInternalUser &&
601*6dbdd20aSAndroid Build Coastguard Worker    trace.sidebar.addMenuItem({
602*6dbdd20aSAndroid Build Coastguard Worker      section: 'current_trace',
603*6dbdd20aSAndroid Build Coastguard Worker      text: 'Share',
604*6dbdd20aSAndroid Build Coastguard Worker      action: async () => await shareTrace(trace),
605*6dbdd20aSAndroid Build Coastguard Worker      icon: 'share',
606*6dbdd20aSAndroid Build Coastguard Worker    });
607*6dbdd20aSAndroid Build Coastguard Worker  trace.sidebar.addMenuItem({
608*6dbdd20aSAndroid Build Coastguard Worker    section: 'current_trace',
609*6dbdd20aSAndroid Build Coastguard Worker    text: 'Download',
610*6dbdd20aSAndroid Build Coastguard Worker    action: () => downloadTrace(trace),
611*6dbdd20aSAndroid Build Coastguard Worker    icon: 'file_download',
612*6dbdd20aSAndroid Build Coastguard Worker    disabled: downloadDisabled,
613*6dbdd20aSAndroid Build Coastguard Worker  });
614*6dbdd20aSAndroid Build Coastguard Worker  trace.sidebar.addMenuItem({
615*6dbdd20aSAndroid Build Coastguard Worker    section: 'convert_trace',
616*6dbdd20aSAndroid Build Coastguard Worker    text: 'Switch to legacy UI',
617*6dbdd20aSAndroid Build Coastguard Worker    action: async () => await openCurrentTraceWithOldUI(trace),
618*6dbdd20aSAndroid Build Coastguard Worker    icon: 'filter_none',
619*6dbdd20aSAndroid Build Coastguard Worker    disabled: downloadDisabled,
620*6dbdd20aSAndroid Build Coastguard Worker  });
621*6dbdd20aSAndroid Build Coastguard Worker  trace.sidebar.addMenuItem({
622*6dbdd20aSAndroid Build Coastguard Worker    section: 'convert_trace',
623*6dbdd20aSAndroid Build Coastguard Worker    text: 'Convert to .json',
624*6dbdd20aSAndroid Build Coastguard Worker    action: async () => await convertTraceToJson(trace),
625*6dbdd20aSAndroid Build Coastguard Worker    icon: 'file_download',
626*6dbdd20aSAndroid Build Coastguard Worker    disabled: downloadDisabled,
627*6dbdd20aSAndroid Build Coastguard Worker  });
628*6dbdd20aSAndroid Build Coastguard Worker  trace.traceInfo.hasFtrace &&
629*6dbdd20aSAndroid Build Coastguard Worker    trace.sidebar.addMenuItem({
630*6dbdd20aSAndroid Build Coastguard Worker      section: 'convert_trace',
631*6dbdd20aSAndroid Build Coastguard Worker      text: 'Convert to .systrace',
632*6dbdd20aSAndroid Build Coastguard Worker      action: async () => await convertTraceToSystrace(trace),
633*6dbdd20aSAndroid Build Coastguard Worker      icon: 'file_download',
634*6dbdd20aSAndroid Build Coastguard Worker      disabled: downloadDisabled,
635*6dbdd20aSAndroid Build Coastguard Worker    });
636*6dbdd20aSAndroid Build Coastguard Worker  trace.sidebar.addMenuItem({
637*6dbdd20aSAndroid Build Coastguard Worker    section: 'support',
638*6dbdd20aSAndroid Build Coastguard Worker    sortOrder: 5,
639*6dbdd20aSAndroid Build Coastguard Worker    text: () =>
640*6dbdd20aSAndroid Build Coastguard Worker      isMetatracingEnabled() ? 'Finalize metatrace' : 'Record metatrace',
641*6dbdd20aSAndroid Build Coastguard Worker    action: () => toggleMetatrace(trace.engine),
642*6dbdd20aSAndroid Build Coastguard Worker    icon: () => (isMetatracingEnabled() ? 'download' : 'fiber_smart_record'),
643*6dbdd20aSAndroid Build Coastguard Worker  });
644*6dbdd20aSAndroid Build Coastguard Worker}
645*6dbdd20aSAndroid Build Coastguard Worker
646*6dbdd20aSAndroid Build Coastguard Worker// Used to deal with fields like the entry name, which can be either a direct
647*6dbdd20aSAndroid Build Coastguard Worker// string or a callback that returns the string.
648*6dbdd20aSAndroid Build Coastguard Workerfunction valueOrCallback<T>(value: T | (() => T)): T;
649*6dbdd20aSAndroid Build Coastguard Workerfunction valueOrCallback<T>(value: T | (() => T) | undefined): T | undefined;
650*6dbdd20aSAndroid Build Coastguard Workerfunction valueOrCallback<T>(value: T | (() => T) | undefined): T | undefined {
651*6dbdd20aSAndroid Build Coastguard Worker  if (value === undefined) return undefined;
652*6dbdd20aSAndroid Build Coastguard Worker  return value instanceof Function ? value() : value;
653*6dbdd20aSAndroid Build Coastguard Worker}
654