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