1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import '../tracks/all_frontend'; 16 17import {applyPatches, Patch} from 'immer'; 18import * as m from 'mithril'; 19 20import {forwardRemoteCalls} from '../base/remote'; 21import {Actions} from '../common/actions'; 22import {LogBoundsKey, LogEntriesKey, LogExistsKey} from '../common/logs'; 23 24import {globals, QuantizedLoad, SliceDetails, ThreadDesc} from './globals'; 25import {HomePage} from './home_page'; 26import {openBufferWithLegacyTraceViewer} from './legacy_trace_viewer'; 27import {RecordPage} from './record_page'; 28import {Router} from './router'; 29import {ViewerPage} from './viewer_page'; 30 31/** 32 * The API the main thread exposes to the controller. 33 */ 34class FrontendApi { 35 constructor(private router: Router) {} 36 37 patchState(patches: Patch[]) { 38 globals.state = applyPatches(globals.state, patches); 39 // If the visible time in the global state has been updated more recently 40 // than the visible time handled by the frontend @ 60fps, update it. This 41 // typically happens when restoring the state from a permalink. 42 globals.frontendLocalState.mergeState(globals.state.frontendLocalState); 43 this.redraw(); 44 } 45 46 // TODO: we can't have a publish method for each batch of data that we don't 47 // want to keep in the global state. Figure out a more generic and type-safe 48 // mechanism to achieve this. 49 50 publishOverviewData(data: {[key: string]: QuantizedLoad | QuantizedLoad[]}) { 51 for (const [key, value] of Object.entries(data)) { 52 if (!globals.overviewStore.has(key)) { 53 globals.overviewStore.set(key, []); 54 } 55 if (value instanceof Array) { 56 globals.overviewStore.get(key)!.push(...value); 57 } else { 58 globals.overviewStore.get(key)!.push(value); 59 } 60 } 61 globals.rafScheduler.scheduleRedraw(); 62 } 63 64 publishTrackData(args: {id: string, data: {}}) { 65 globals.setTrackData(args.id, args.data); 66 if ([LogExistsKey, LogBoundsKey, LogEntriesKey].includes(args.id)) { 67 globals.rafScheduler.scheduleFullRedraw(); 68 } else { 69 globals.rafScheduler.scheduleRedraw(); 70 } 71 } 72 73 publishQueryResult(args: {id: string, data: {}}) { 74 globals.queryResults.set(args.id, args.data); 75 this.redraw(); 76 } 77 78 publishThreads(data: ThreadDesc[]) { 79 globals.threads.clear(); 80 data.forEach(thread => { 81 globals.threads.set(thread.utid, thread); 82 }); 83 this.redraw(); 84 } 85 86 publishSliceDetails(click: SliceDetails) { 87 globals.sliceDetails = click; 88 this.redraw(); 89 } 90 91 // For opening JSON/HTML traces with the legacy catapult viewer. 92 publishLegacyTrace(args: {data: ArrayBuffer, size: number}) { 93 const arr = new Uint8Array(args.data, 0, args.size); 94 const str = (new TextDecoder('utf-8')).decode(arr); 95 openBufferWithLegacyTraceViewer('trace.json', str, 0); 96 } 97 98 private redraw(): void { 99 if (globals.state.route && 100 globals.state.route !== this.router.getRouteFromHash()) { 101 this.router.setRouteOnHash(globals.state.route); 102 } 103 104 globals.rafScheduler.scheduleFullRedraw(); 105 } 106} 107 108function main() { 109 const controller = new Worker('controller_bundle.js'); 110 controller.onerror = e => { 111 console.error(e); 112 }; 113 const channel = new MessageChannel(); 114 controller.postMessage(channel.port1, [channel.port1]); 115 const dispatch = controller.postMessage.bind(controller); 116 const router = new Router( 117 '/', 118 { 119 '/': HomePage, 120 '/viewer': ViewerPage, 121 '/record': RecordPage, 122 }, 123 dispatch); 124 forwardRemoteCalls(channel.port2, new FrontendApi(router)); 125 globals.initialize(dispatch, controller); 126 127 globals.rafScheduler.domRedraw = () => 128 m.render(document.body, m(router.resolve(globals.state.route))); 129 130 131 // Put these variables in the global scope for better debugging. 132 (window as {} as {m: {}}).m = m; 133 (window as {} as {globals: {}}).globals = globals; 134 135 // /?s=xxxx for permalinks. 136 const stateHash = Router.param('s'); 137 if (stateHash) { 138 globals.dispatch(Actions.loadPermalink({ 139 hash: stateHash, 140 })); 141 } 142 143 // Prevent pinch zoom. 144 document.body.addEventListener('wheel', (e: MouseEvent) => { 145 if (e.ctrlKey) e.preventDefault(); 146 }, {passive: false}); 147 148 router.navigateToCurrentHash(); 149} 150 151main(); 152