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