[ui] Make track registry available to core

- Move track registry from PluginManager to TrackCache.
- Rename TrackCache -> TrackManger.
- Initialize TrackManager in globals instead of ViewerPage.
- This change allows things in Perfetto core to register tracks.

Change-Id: Ia008c2dc3c366d2966d6365ac086a5ed17d561d7
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 1cc722a..40c3c4b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -22,6 +22,7 @@
   GenericSliceDetailsTabConfig,
   GenericSliceDetailsTabConfigBase,
 } from '../frontend/generic_slice_details_tab';
+import {globals} from '../frontend/globals';
 import {
   Aggregation,
   AggregationFunction,
@@ -46,7 +47,6 @@
   traceEventEnd,
   TraceEventScope,
 } from './metatracing';
-import {pluginManager} from './plugins';
 import {
   AdbRecordingTarget,
   Area,
@@ -219,7 +219,7 @@
     if (exists(uri)) {
       // If track is a new "plugin" type track (i.e. it has a uri), resolve the
       // track ids from through the pluginManager.
-      const trackInfo = pluginManager.resolveTrackInfo(uri);
+      const trackInfo = globals.trackManager.resolveTrackInfo(uri);
       if (trackInfo?.trackIds) {
         for (const trackId of trackInfo.trackIds) {
           setTrackKey(trackId, trackKey);
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index d9037c6..9535b08 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -110,8 +110,6 @@
 
   constructor(
       private ctx: PluginContext, readonly engine: EngineProxy,
-      readonly trackRegistry: Map<string, TrackDescriptor>,
-      private defaultTracks: Set<TrackRef>,
       private commandRegistry: Map<string, Command>) {
     this.trash.add(engine);
   }
@@ -134,13 +132,15 @@
   registerTrack(trackDesc: TrackDescriptor): void {
     // Silently ignore if context is dead.
     if (!this.alive) return;
-    this.trackRegistry.set(trackDesc.uri, trackDesc);
-    this.trash.addCallback(() => this.trackRegistry.delete(trackDesc.uri));
+    globals.trackManager.registerTrack(trackDesc);
+    this.trash.addCallback(
+      () => globals.trackManager.unregisterTrack(trackDesc.uri));
   }
 
   addDefaultTrack(track: TrackRef): void {
-    this.defaultTracks.add(track);
-    this.trash.addCallback(() => this.defaultTracks.delete(track));
+    globals.trackManager.addDefaultTrack(track);
+    this.trash.addCallback(
+      () => globals.trackManager.removeDefaultTrack(track));
   }
 
   registerStaticTrack(track: TrackDescriptor&TrackRef): void {
@@ -337,9 +337,7 @@
   private registry: PluginRegistry;
   private plugins: Map<string, PluginDetails>;
   private engine?: Engine;
-  readonly trackRegistry = new Map<string, TrackDescriptor>();
   readonly commandRegistry = new Map<string, Command>();
-  readonly defaultTracks = new Set<TrackRef>();
 
   constructor(registry: PluginRegistry) {
     this.registry = registry;
@@ -395,10 +393,6 @@
     return this.plugins.get(pluginId);
   }
 
-  findPotentialTracks(): TrackRef[] {
-    return Array.from(this.defaultTracks);
-  }
-
   async onTraceLoad(engine: Engine): Promise<void> {
     this.engine = engine;
     const plugins = Array.from(this.plugins.entries());
@@ -430,12 +424,6 @@
     });
   }
 
-  // Look up track into for a given track's URI.
-  // Returns |undefined| if no track can be found.
-  resolveTrackInfo(uri: string): TrackDescriptor|undefined {
-    return this.trackRegistry.get(uri);
-  }
-
   private async doPluginTraceLoad(
     pluginDetails: PluginDetails, engine: Engine,
     pluginId: string): Promise<void> {
@@ -443,12 +431,8 @@
 
     const engineProxy = engine.getProxy(pluginId);
 
-    const traceCtx = new PluginContextTraceImpl(
-      context,
-      engineProxy,
-      this.trackRegistry,
-      this.defaultTracks,
-      this.commandRegistry);
+    const traceCtx =
+        new PluginContextTraceImpl(context, engineProxy, this.commandRegistry);
     pluginDetails.traceContext = traceCtx;
 
     const result = plugin.onTraceLoad?.(traceCtx);
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
index 69f649a..ee1d113 100644
--- a/ui/src/common/track_cache.ts
+++ b/ui/src/common/track_cache.ts
@@ -12,8 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {globals} from '../frontend/globals';
-import {Migrate, Track, TrackContext, TrackDescriptor} from '../public';
+import {Store} from '../frontend/store';
+import {
+  Migrate,
+  Track,
+  TrackContext,
+  TrackDescriptor,
+  TrackRef,
+} from '../public';
+
+import {State} from './state';
 
 export interface TrackCacheEntry {
   track: Track;
@@ -43,9 +51,46 @@
 //   flushTracks() <-- 'bar' is destroyed, as it was not resolved this cycle
 // Third cycle
 //   flushTracks() <-- 'foo' is destroyed.
-export class TrackCache {
+export class TrackManager {
   private safeCache = new Map<string, TrackCacheEntry>();
   private recycleBin = new Map<string, TrackCacheEntry>();
+  private trackRegistry = new Map<string, TrackDescriptor>();
+  private defaultTracks = new Set<TrackRef>();
+  private store: Store<State>;
+
+  constructor(store: Store<State>) {
+    this.store = store;
+  }
+
+  registerTrack(trackDesc: TrackDescriptor): void {
+    this.trackRegistry.set(trackDesc.uri, trackDesc);
+  }
+
+  unregisterTrack(uri: string): void {
+    this.trackRegistry.delete(uri);
+  }
+
+  addDefaultTrack(track: TrackRef): void {
+    this.defaultTracks.add(track);
+  }
+
+  removeDefaultTrack(track: TrackRef): void {
+    this.defaultTracks.delete(track);
+  }
+
+  findPotentialTracks(): TrackRef[] {
+    return Array.from(this.defaultTracks);
+  }
+
+  getAllTracks(): TrackDescriptor[] {
+    return Array.from(this.trackRegistry.values());
+  }
+
+  // Look up track into for a given track's URI.
+  // Returns |undefined| if no track can be found.
+  resolveTrackInfo(uri: string): TrackDescriptor|undefined {
+    return this.trackRegistry.get(uri);
+  }
 
   // Creates a new track using |uri| and |params| or retrieves a cached track if
   // |key| exists in the cache.
@@ -72,7 +117,7 @@
         trackKey: key,
         mountStore: <T>(migrate: Migrate<T>) => {
           const path = ['tracks', key, 'state'];
-          return globals.store.createSubStore(path, migrate);
+          return this.store.createSubStore(path, migrate);
         },
         params,
       };
diff --git a/ui/src/common/track_cache_unittest.ts b/ui/src/common/track_cache_unittest.ts
index 951ebdc..0fb2608 100644
--- a/ui/src/common/track_cache_unittest.ts
+++ b/ui/src/common/track_cache_unittest.ts
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Track, TrackDescriptor} from '../public';
+import {createStore, Track, TrackDescriptor} from '../public';
 
-import {TrackCache} from './track_cache';
+import {createEmptyState} from './empty_state';
+import {TrackManager} from './track_cache';
 
 function makeMockTrack(): Track {
   return {
@@ -37,15 +38,22 @@
   await new Promise((r) => setTimeout(r, 0));
 }
 
+let track: Track;
+let td: TrackDescriptor;
+let trackCache: TrackManager;
+
+beforeEach(() => {
+  track = makeMockTrack();
+  td = {
+    uri: 'test',
+    track: () => track,
+  };
+  const store = createStore(createEmptyState());
+  trackCache = new TrackManager(store);
+});
+
 describe('TrackCache', () => {
   it('calls track lifecycle hooks', async () => {
-    const track = makeMockTrack();
-    const td: TrackDescriptor = {
-      uri: 'test',
-      track: () => track,
-    };
-    const trackCache = new TrackCache();
-
     const entry = trackCache.resolveTrack('foo', td);
     entry.update();
     await settle();
@@ -53,13 +61,6 @@
   });
 
   it('reuses tracks', async () => {
-    const track = makeMockTrack();
-    const td: TrackDescriptor = {
-      uri: 'test',
-      track: () => track,
-    };
-    const trackCache = new TrackCache();
-
     const first = trackCache.resolveTrack('foo', td);
     trackCache.flushOldTracks();
     const second = trackCache.resolveTrack('foo', td);
@@ -71,13 +72,6 @@
   });
 
   it('destroys tracks', async () => {
-    const track = makeMockTrack();
-    const td: TrackDescriptor = {
-      uri: 'test',
-      track: () => track,
-    };
-    const trackCache = new TrackCache();
-
     trackCache.resolveTrack('foo', td);
 
     // Double flush should destroy all tracks
@@ -93,13 +87,6 @@
 
 describe('TrackCacheEntry', () => {
   it('updates', async () => {
-    const track = makeMockTrack();
-    const td: TrackDescriptor = {
-      uri: 'test',
-      track: () => track,
-    };
-    const trackCache = new TrackCache();
-
     const entry = trackCache.resolveTrack('foo', td);
     entry.update();
     await settle();
@@ -107,13 +94,6 @@
   });
 
   it('throws if updated when destroyed', async () => {
-    const track = makeMockTrack();
-    const td: TrackDescriptor = {
-      uri: 'test',
-      track: () => track,
-    };
-    const trackCache = new TrackCache();
-
     const entry = trackCache.resolveTrack('foo', td);
 
     // Double flush should destroy all tracks
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index 28b5b8d..8ed61e4 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -14,7 +14,6 @@
 
 import {Duration} from '../../base/time';
 import {ColumnDef} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
@@ -30,7 +29,7 @@
     for (const trackKey of area.tracks) {
       const track = globals.state.tracks[trackKey];
       if (track?.uri) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === COUNTER_TRACK_KIND) {
           trackInfo.trackIds && trackIds.push(...trackInfo.trackIds);
         }
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index c0ddbe3..bcf1967 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -14,7 +14,6 @@
 
 import {exists} from '../../base/utils';
 import {ColumnDef} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
@@ -31,7 +30,7 @@
     for (const trackKey of area.tracks) {
       const track = globals.state.tracks[trackKey];
       if (track?.uri) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
           exists(trackInfo.cpu) && selectedCpus.push(trackInfo.cpu);
         }
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index b58d5b3..df18d98 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -14,7 +14,6 @@
 
 import {exists} from '../../base/utils';
 import {ColumnDef} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
@@ -30,7 +29,7 @@
     for (const trackKey of area.tracks) {
       const track = globals.state.tracks[trackKey];
       if (track?.uri) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
           exists(trackInfo.cpu) && selectedCpus.push(trackInfo.cpu);
         }
diff --git a/ui/src/controller/aggregation/frame_aggregation_controller.ts b/ui/src/controller/aggregation/frame_aggregation_controller.ts
index c45a40c..fc3c2ca 100644
--- a/ui/src/controller/aggregation/frame_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/frame_aggregation_controller.ts
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 import {ColumnDef} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
@@ -30,7 +29,7 @@
       const track = globals.state.tracks[trackKey];
       // Track will be undefined for track groups.
       if (track?.uri !== undefined) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
           trackInfo.trackIds && selectedSqlTrackIds.push(...trackInfo.trackIds);
         }
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 7543110..baf2057 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 import {ColumnDef} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
@@ -30,7 +29,7 @@
     const track = globals.state.tracks[trackKey];
     // Track will be undefined for track groups.
     if (track?.uri !== undefined) {
-      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
       if (trackInfo?.kind === SLICE_TRACK_KIND) {
         trackInfo.trackIds && selectedTrackKeys.push(...trackInfo.trackIds);
       }
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index 9906fab..24fede2 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -14,7 +14,6 @@
 
 import {exists} from '../../base/utils';
 import {ColumnDef, ThreadStateExtra} from '../../common/aggregation_data';
-import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {translateState} from '../../common/thread_state';
 import {globals} from '../../frontend/globals';
@@ -33,7 +32,7 @@
       const track = globals.state.tracks[trackId];
       // Track will be undefined for track groups.
       if (track?.uri) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === THREAD_STATE_TRACK_KIND) {
           exists(trackInfo.utid) && this.utids.push(trackInfo.utid);
         }
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index e856d72..5512d86 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -21,7 +21,6 @@
   findRootSize,
   mergeCallsites,
 } from '../common/flamegraph_util';
-import {pluginManager} from '../common/plugins';
 import {
   CallsiteInfo,
   FlamegraphState,
@@ -134,7 +133,7 @@
       for (const trackId of area.tracks) {
         const track = globals.state.tracks[trackId];
         if (track?.uri) {
-          const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+          const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
           if (trackInfo?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND) {
             exists(trackInfo.upid) && upids.push(trackInfo.upid);
           }
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 202a626..5af5935 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 import {Time} from '../base/time';
-import {pluginManager} from '../common/plugins';
 import {Area} from '../common/state';
 import {featureFlags} from '../core/feature_flags';
 import {Flow, globals} from '../frontend/globals';
@@ -233,7 +232,7 @@
       // anything if there is only one TP track in this async track. In
       // that case experimental_slice_layout is just an expensive way
       // to find out depth === layout_depth.
-      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
       const trackIds = trackInfo?.trackIds;
       if (trackIds === undefined || trackIds.length <= 1) {
         uiTrackIdToInfo.set(uiTrackId, null);
@@ -378,7 +377,7 @@
     for (const uiTrackId of area.tracks) {
       const track = globals.state.tracks[uiTrackId];
       if (track?.uri !== undefined) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         const kind = trackInfo?.kind;
         if (kind === SLICE_TRACK_KIND ||
             kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 937a4d4..89b1973 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -22,7 +22,6 @@
   TimeSpan,
 } from '../base/time';
 import {exists} from '../base/utils';
-import {pluginManager} from '../common/plugins';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 import {OmniboxState} from '../common/state';
 import {globals} from '../frontend/globals';
@@ -203,7 +202,7 @@
     const cpuToTrackId = new Map();
     for (const track of Object.values(globals.state.tracks)) {
       if (exists(track?.uri)) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
           exists(trackInfo.cpu) && cpuToTrackId.set(trackInfo.cpu, track.key);
         }
@@ -283,7 +282,8 @@
       } else if (it.source === 'log') {
         const logTracks =
             Object.values(globals.state.tracks).filter((track) => {
-              const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+              const trackDesc =
+                  globals.trackManager.resolveTrackInfo(track.uri);
               return (trackDesc && trackDesc.kind === 'AndroidLogTrack');
             });
         if (logTracks.length > 0) {
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 09753b2..d2bf5ba 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -15,7 +15,6 @@
 import {assertTrue} from '../base/logging';
 import {Time, time} from '../base/time';
 import {Args, ArgValue} from '../common/arg_types';
-import {pluginManager} from '../common/plugins';
 import {ChromeSliceSelection} from '../common/state';
 import {
   CounterDetails,
@@ -310,7 +309,7 @@
     // UI track id for slice tracks this would be unnecessary.
     let trackKey = '';
     for (const track of Object.values(globals.state.tracks)) {
-      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
       if (trackInfo?.kind === SLICE_TRACK_KIND) {
         const trackIds = trackInfo?.trackIds;
         if (trackIds && trackIds.length > 0 && trackIds[0] === trackId) {
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 4ef5258..d61d7c4 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -21,7 +21,6 @@
   AddTrackArgs,
   DeferredAction,
 } from '../common/actions';
-import {pluginManager} from '../common/plugins';
 import {
   InThreadTrackSortKey,
   SCROLLING_TRACK_GROUP,
@@ -29,6 +28,7 @@
   UtidToTrackSortKey,
 } from '../common/state';
 import {featureFlags, PERF_SAMPLE_FLAG} from '../core/feature_flags';
+import {globals} from '../frontend/globals';
 import {PrimaryTrackSortKey} from '../public';
 import {getTrackName} from '../public/utils';
 import {Engine, EngineProxy} from '../trace_processor/engine';
@@ -1729,7 +1729,7 @@
   }
 
   addPluginTracks(): void {
-    const tracks = pluginManager.findPotentialTracks();
+    const tracks = globals.trackManager.findPotentialTracks();
     for (const info of tracks) {
       this.tracksToAdd.push({
         uri: info.uri,
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 39392c7..e3bae9a 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -28,7 +28,6 @@
   TimeSpan,
 } from '../base/time';
 import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
 import {runQuery} from '../common/queries';
 import {
   DurationPrecision,
@@ -245,7 +244,7 @@
 
       if (firstThreadStateTrack) {
         const trackInfo = globals.state.tracks[firstThreadStateTrack];
-        const trackDesc = pluginManager.resolveTrackInfo(trackInfo.uri);
+        const trackDesc = globals.trackManager.resolveTrackInfo(trackInfo.uri);
         if (trackDesc?.kind === THREAD_STATE_TRACK_KIND &&
             trackDesc?.utid !== undefined) {
           return trackDesc?.utid;
@@ -507,7 +506,7 @@
       name: 'Find track by URI',
       callback:
           async () => {
-            const tracks = Array.from(pluginManager.trackRegistry.values());
+            const tracks = globals.trackManager.getAllTracks();
             const options = tracks.map(({uri}): PromptOption => {
               return {key: uri, displayName: uri};
             });
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index ff0af3c..fc3083d 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -14,7 +14,6 @@
 
 import {time} from '../base/time';
 import {exists} from '../base/utils';
-import {pluginManager} from '../common/plugins';
 import {TrackState} from '../common/state';
 import {SliceRect} from '../public';
 
@@ -53,7 +52,7 @@
 }
 
 function getTrackIds(track: TrackState): number[] {
-  const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+  const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
   return trackDesc?.trackIds ?? [];
 }
 
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index fb7b027..205beab 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -29,6 +29,7 @@
   ConversionJobName,
   ConversionJobStatus,
 } from '../common/conversion_jobs';
+import {createEmptyState} from '../common/empty_state';
 import {
   HighPrecisionTime,
   HighPrecisionTimeSpan,
@@ -45,6 +46,7 @@
 } from '../common/state';
 import {TabManager} from '../common/tab_registry';
 import {TimestampFormat, timestampFormat} from '../common/timestamp_format';
+import {TrackManager} from '../common/track_cache';
 import {setPerfHooks} from '../core/perf';
 import {raf} from '../core/raf_scheduler';
 import {Engine} from '../trace_processor/engine';
@@ -248,7 +250,7 @@
 
   private _testing = false;
   private _dispatch?: Dispatch = undefined;
-  private _store?: Store<State>;
+  private _store = createStore(createEmptyState());
   private _timeline?: Timeline = undefined;
   private _serviceWorkerController?: ServiceWorkerController = undefined;
   private _logging?: Analytics = undefined;
@@ -286,6 +288,7 @@
   private _traceTzOffset = Time.ZERO;
   private _openQueryHandler?: OpenQueryHandler;
   private _tabManager = new TabManager();
+  private _trackManager = new TrackManager(this._store);
 
   scrollToTrackKey?: string|number;
   httpRpcState: HttpRpcState = {connected: false};
@@ -311,12 +314,9 @@
 
   engines = new Map<string, Engine>();
 
-  initialize(
-    dispatch: Dispatch, router: Router, initialState: State,
-    cmdManager: CommandManager) {
+  initialize(dispatch: Dispatch, router: Router, cmdManager: CommandManager) {
     this._dispatch = dispatch;
     this._router = router;
-    this._store = createStore(initialState);
     this._cmdManager = cmdManager;
     this._timeline = new Timeline();
 
@@ -660,7 +660,6 @@
 
   resetForTesting() {
     this._dispatch = undefined;
-    this._store = undefined;
     this._timeline = undefined;
     this._serviceWorkerController = undefined;
 
@@ -784,6 +783,10 @@
     return this._tabManager;
   }
 
+  get trackManager() {
+    return this._trackManager;
+  }
+
   // Offset between t=0 and the configured time domain.
   timestampOffset(): time {
     const fmt = timestampFormat();
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index f4f3558..56e3437 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -23,7 +23,6 @@
 import {addErrorHandler, reportError} from '../base/logging';
 import {Actions, DeferredAction, StateActions} from '../common/actions';
 import {CommandManager} from '../common/commands';
-import {createEmptyState} from '../common/empty_state';
 import {flattenArgs, traceEvent} from '../common/metatracing';
 import {pluginManager, pluginRegistry} from '../common/plugins';
 import {State} from '../common/state';
@@ -250,7 +249,7 @@
 
   const cmdManager = new CommandManager();
 
-  globals.initialize(dispatch, router, createEmptyState(), cmdManager);
+  globals.initialize(dispatch, router, cmdManager);
 
   globals.serviceWorkerController.install();
 
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index cdd4e4a..a725077 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -15,7 +15,6 @@
 import m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
 import {translateState} from '../common/thread_state';
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 import {Anchor} from '../widgets/anchor';
@@ -208,7 +207,7 @@
 
     let trackKey: string|number|undefined;
     for (const track of Object.values(globals.state.tracks)) {
-      const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+      const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
       // TODO(stevegolton): Handle v2.
       if (trackDesc && trackDesc.kind === THREAD_STATE_TRACK_KIND &&
           trackDesc.utid === threadInfo.utid) {
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index c3c89de..3f1eb17 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -22,7 +22,6 @@
 } from '../base/time';
 import {exists} from '../base/utils';
 import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
 import {translateState} from '../common/thread_state';
 import {EngineProxy} from '../trace_processor/engine';
 import {LONG, NUM, NUM_NULL, STR_NULL} from '../trace_processor/query_result';
@@ -147,7 +146,7 @@
   let trackId: string|undefined;
   for (const track of Object.values(globals.state.tracks)) {
     if (exists(track?.uri)) {
-      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
       if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
         if (trackInfo?.cpu === cpu) {
           trackId = track.key;
@@ -181,7 +180,8 @@
         onclick: () => {
           let trackKey: string|number|undefined;
           for (const track of Object.values(globals.state.tracks)) {
-            const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+            const trackDesc =
+                  globals.trackManager.resolveTrackInfo(track.uri);
             if (trackDesc && trackDesc.kind === THREAD_STATE_TRACK_KIND &&
                   trackDesc.utid === vnode.attrs.utid) {
               trackKey = track.key;
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 0d43f98..5f918af 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -18,8 +18,7 @@
 import {clamp} from '../base/math_utils';
 import {Time} from '../base/time';
 import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
-import {TrackCache, TrackCacheEntry} from '../common/track_cache';
+import {TrackCacheEntry} from '../common/track_cache';
 import {featureFlags} from '../core/feature_flags';
 import {raf} from '../core/raf_scheduler';
 import {TrackTags} from '../public';
@@ -90,8 +89,6 @@
   // Used to prevent global deselection if a pan/drag select occurred.
   private keepCurrentSelection = false;
 
-  readonly trackCache = new TrackCache();
-
   private overviewTimelinePanel = new OverviewTimelinePanel('overview');
   private timeAxisPanel = new TimeAxisPanel('timeaxis');
   private timeSelectionPanel = new TimeSelectionPanel('timeselection');
@@ -332,7 +329,7 @@
           })))),
       this.renderTabPanel());
 
-    this.trackCache.flushOldTracks();
+    globals.trackManager.flushOldTracks();
     return result;
   }
 
@@ -340,9 +337,9 @@
   private resolveTrack(key: string): TrackBundle {
     const trackState = globals.state.tracks[key];
     const {uri, params, name, labels} = trackState;
-    const trackDesc = pluginManager.resolveTrackInfo(uri);
+    const trackDesc = globals.trackManager.resolveTrackInfo(uri);
     const trackCacheEntry =
-        trackDesc && this.trackCache.resolveTrack(key, trackDesc, params);
+        trackDesc && globals.trackManager.resolveTrack(key, trackDesc, params);
     const trackFSM = trackCacheEntry;
     const tags = trackCacheEntry?.desc.tags;
     const trackIds = trackCacheEntry?.desc.trackIds;