[ui] Refactor `NewBottomTabArgs` interface to use generic type for
`config` property.

This provides type safety when constructing bottom tabs manually, and
avoids a type assertion in the bottom tab base class.

Bug: 320916474
Change-Id: I29ef9d42cf918c4e103267f9a5718d0966256b27
diff --git a/ui/src/frontend/bottom_tab.ts b/ui/src/frontend/bottom_tab.ts
index a79cfe9..6cba1e2 100644
--- a/ui/src/frontend/bottom_tab.ts
+++ b/ui/src/frontend/bottom_tab.ts
@@ -25,11 +25,11 @@
 
 import {globals} from './globals';
 
-export interface NewBottomTabArgs {
+export interface NewBottomTabArgs<Config> {
   engine: EngineProxy;
   tag?: string;
   uuid: string;
-  config: {};
+  config: Config;
 }
 
 // Interface for allowing registration and creation of bottom tabs.
@@ -37,7 +37,7 @@
 export interface BottomTabCreator {
   readonly kind: string;
 
-  create(args: NewBottomTabArgs): BottomTab;
+  create(args: NewBottomTabArgs<unknown>): BottomTab;
 }
 
 export const bottomTabRegistry = Registry.kindRegistry<BottomTabCreator>();
@@ -78,8 +78,8 @@
   // panel.
   readonly uuid: string;
 
-  constructor(args: NewBottomTabArgs) {
-    this.config = args.config as Config;
+  constructor(args: NewBottomTabArgs<Config>) {
+    this.config = args.config;
     this.engine = args.engine;
     this.tag = args.tag;
     this.uuid = args.uuid;
@@ -110,7 +110,7 @@
 // lifecycle events. Most cases, however, don't need them and BottomTab
 // provides a simplified API for the common case.
 export abstract class BottomTab<Config = {}> extends BottomTabBase<Config> {
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<Config>) {
     super(args);
   }
 
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 8b0066f..f5e5f160 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -231,11 +231,12 @@
   private sliceDetails?: SliceDetails;
   private breakdownByThreadState?: BreakdownByThreadState;
 
-  static create(args: NewBottomTabArgs): ChromeSliceDetailsTab {
+  static create(args: NewBottomTabArgs<ChromeSliceDetailsTabConfig>):
+      ChromeSliceDetailsTab {
     return new ChromeSliceDetailsTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<ChromeSliceDetailsTabConfig>) {
     super(args);
     this.load();
   }
diff --git a/ui/src/frontend/generic_slice_details_tab.ts b/ui/src/frontend/generic_slice_details_tab.ts
index 93515cf..e7ceb20 100644
--- a/ui/src/frontend/generic_slice_details_tab.ts
+++ b/ui/src/frontend/generic_slice_details_tab.ts
@@ -53,11 +53,12 @@
 
   data: {[key: string]: ColumnType}|undefined;
 
-  static create(args: NewBottomTabArgs): GenericSliceDetailsTab {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      GenericSliceDetailsTab {
     return new GenericSliceDetailsTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
 
     this.engine
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 4078db1..4b41a17 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -313,11 +313,11 @@
 export class NotesEditorTab extends BottomTab<NotesEditorTabConfig> {
   static readonly kind = 'org.perfetto.NotesEditorTab';
 
-  static create(args: NewBottomTabArgs): NotesEditorTab {
+  static create(args: NewBottomTabArgs<NotesEditorTabConfig>): NotesEditorTab {
     return new NotesEditorTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<NotesEditorTabConfig>) {
     super(args);
   }
 
diff --git a/ui/src/frontend/query_result_tab.ts b/ui/src/frontend/query_result_tab.ts
index f0efe26..eb51c6e 100644
--- a/ui/src/frontend/query_result_tab.ts
+++ b/ui/src/frontend/query_result_tab.ts
@@ -64,17 +64,17 @@
   queryResponse?: QueryResponse;
   sqlViewName?: string;
 
-  static create(args: NewBottomTabArgs): QueryResultTab {
+  static create(args: NewBottomTabArgs<QueryResultTabConfig>): QueryResultTab {
     return new QueryResultTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<QueryResultTabConfig>) {
     super(args);
 
     this.initTrack(args);
   }
 
-  async initTrack(args: NewBottomTabArgs) {
+  async initTrack(args: NewBottomTabArgs<QueryResultTabConfig>) {
     let uuid = '';
     if (this.config.prefetchedResponse !== undefined) {
       this.queryResponse = this.config.prefetchedResponse;
diff --git a/ui/src/frontend/sql_table/tab.ts b/ui/src/frontend/sql_table/tab.ts
index 1629d42..5795b39 100644
--- a/ui/src/frontend/sql_table/tab.ts
+++ b/ui/src/frontend/sql_table/tab.ts
@@ -38,14 +38,14 @@
 
   private state: SqlTableState;
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<SqlTableTabConfig>) {
     super(args);
 
     this.state =
         new SqlTableState(this.engine, this.config.table, this.config.filters);
   }
 
-  static create(args: NewBottomTabArgs): SqlTableTab {
+  static create(args: NewBottomTabArgs<SqlTableTabConfig>): SqlTableTab {
     return new SqlTableTab(args);
   }
 
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 8f43379..da96c65 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -63,11 +63,11 @@
   relatedStates?: RelatedThreadStates;
   loaded: boolean = false;
 
-  static create(args: NewBottomTabArgs): ThreadStateTab {
+  static create(args: NewBottomTabArgs<ThreadStateTabConfig>): ThreadStateTab {
     return new ThreadStateTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<ThreadStateTabConfig>) {
     super(args);
 
     this.load().then(() => {
diff --git a/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts
index dabfedd..9a95c4d 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts
@@ -34,11 +34,12 @@
   static readonly kind = 'org.perfetto.PageLoadDetailsPanel';
   private data: Details;
 
-  static create(args: NewBottomTabArgs): PageLoadDetailsPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      PageLoadDetailsPanel {
     return new PageLoadDetailsPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.data = new Details(
         this.engine,
diff --git a/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts
index dbcde70..97a0db2 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts
@@ -48,11 +48,12 @@
   private loaded = false;
   private data: Data|undefined;
 
-  static create(args: NewBottomTabArgs): StartupDetailsPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      StartupDetailsPanel {
     return new StartupDetailsPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.loadData();
   }
diff --git a/ui/src/tracks/chrome_critical_user_interactions/web_content_interaction_details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
index 2e7ae06..fa1641f 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
@@ -61,11 +61,12 @@
   private loaded = false;
   private data: Data|undefined;
 
-  static create(args: NewBottomTabArgs): WebContentInteractionPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      WebContentInteractionPanel {
     return new WebContentInteractionPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.loadData();
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
index b30000b..e8a97de 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
@@ -77,11 +77,12 @@
   private relevantThreadStage: EventLatencyStage|undefined;
   private relevantThreadTracks: EventLatencyCauseThreadTracks[] = [];
 
-  static create(args: NewBottomTabArgs): EventLatencySliceDetailsPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      EventLatencySliceDetailsPanel {
     return new EventLatencySliceDetailsPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
 
     this.loadData();
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
index 2837ce1..9f89c35 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
@@ -93,11 +93,12 @@
   orderedJankSlices: JankSliceDetails[] = [];
   scrollDeltas: m.Child;
 
-  static create(args: NewBottomTabArgs): ScrollDetailsPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      ScrollDetailsPanel {
     return new ScrollDetailsPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.loadData();
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
index d130151..e1218ef 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
@@ -98,11 +98,12 @@
   // slice. Does not apply to all causes.
   private subcauseSliceDetails?: EventLatencySlice;
 
-  static create(args: NewBottomTabArgs): ScrollJankV3DetailsPanel {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      ScrollJankV3DetailsPanel {
     return new ScrollJankV3DetailsPanel(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.loadData();
   }
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index 33ac847..cb1f1ca 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -103,7 +103,8 @@
   threadState?: ThreadState;
   slice?: SliceDetails;
 
-  static create(args: NewBottomTabArgs): DebugSliceDetailsTab {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      DebugSliceDetailsTab {
     return new DebugSliceDetailsTab(args);
   }
 
@@ -224,7 +225,7 @@
     raf.scheduleRedraw();
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     this.loadData();
   }
diff --git a/ui/src/tracks/screenshots/screenshot_panel.ts b/ui/src/tracks/screenshots/screenshot_panel.ts
index fd09453..28d0889 100644
--- a/ui/src/tracks/screenshots/screenshot_panel.ts
+++ b/ui/src/tracks/screenshots/screenshot_panel.ts
@@ -38,11 +38,12 @@
 
   private sliceDetails?: SliceDetails;
 
-  static create(args: NewBottomTabArgs): ScreenshotTab {
+  static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
+      ScreenshotTab {
     return new ScreenshotTab(args);
   }
 
-  constructor(args: NewBottomTabArgs) {
+  constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
     super(args);
     getSliceDetails(this.engine, this.config.id)
         .then((sliceDetails) => this.sliceDetails = sliceDetails);