• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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