xref: /aosp_15_r20/external/perfetto/ui/src/core/page_manager.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 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 m from 'mithril';
16import {assertExists, assertTrue} from '../base/logging';
17import {Registry} from '../base/registry';
18import {PageAttrs, PageHandler, PageWithTraceAttrs} from '../public/page';
19import {Router} from './router';
20import {TraceImpl} from './trace_impl';
21
22export interface PageWithTraceImplAttrs extends PageAttrs {
23  trace: TraceImpl;
24}
25
26// This is to allow internal core classes to get a TraceImpl injected rather
27// than just a Trace.
28type PageHandlerInternal = PageHandler<
29  | m.ComponentTypes<PageWithTraceAttrs>
30  | m.ComponentTypes<PageWithTraceImplAttrs>
31>;
32
33export class PageManagerImpl {
34  private readonly registry = new Registry<PageHandlerInternal>((x) => x.route);
35
36  registerPage(pageHandler: PageHandlerInternal): Disposable {
37    assertTrue(/^\/\w*$/.exec(pageHandler.route) !== null);
38    // The pluginId is injected by the proxy in AppImpl / TraceImpl. If this is
39    // undefined somebody (tests) managed to call this method without proxy.
40    assertExists(pageHandler.pluginId);
41    return this.registry.register(pageHandler);
42  }
43
44  // Called by index.ts upon the main frame redraw callback.
45  renderPageForCurrentRoute(
46    trace: TraceImpl | undefined,
47  ): m.Vnode<PageAttrs> | m.Vnode<PageWithTraceImplAttrs> {
48    const route = Router.parseFragment(location.hash);
49    const res = this.renderPageForRoute(trace, route.page, route.subpage);
50    if (res !== undefined) {
51      return res;
52    }
53    // If either the route doesn't exist or requires a trace but the trace is
54    // not loaded, fall back on the default route /.
55    return assertExists(this.renderPageForRoute(trace, '/', ''));
56  }
57
58  // Will return undefined if either: (1) the route does not exist; (2) the
59  // route exists, it requires a trace, but there is no trace loaded.
60  private renderPageForRoute(
61    coreTrace: TraceImpl | undefined,
62    page: string,
63    subpage: string,
64  ) {
65    const handler = this.registry.tryGet(page);
66    if (handler === undefined) {
67      return undefined;
68    }
69    const pluginId = assertExists(handler?.pluginId);
70    const trace = coreTrace?.forkForPlugin(pluginId);
71    const traceRequired = !handler?.traceless;
72    if (traceRequired && trace === undefined) {
73      return undefined;
74    }
75    if (traceRequired) {
76      return m(handler.page as m.ComponentTypes<PageWithTraceImplAttrs>, {
77        subpage,
78        trace: assertExists(trace),
79      });
80    }
81    return m(handler.page, {subpage, trace});
82  }
83}
84