ui: Remove the controller dispatch() layer

The existence of two different globals.ts files:
- ui/src/controller/globals.ts
- ui/src/frontent/globals.ts
is a source of confusion and, now the controller worker is no more,
completely unnecessary. We should remove ui/src/controller/globals.ts.

The first step for that is to remove the legacy dispatch() method
on controllers/globals and instead have controllers invoke the
frontent/globals version directly.

Change-Id: Iea6083f2834d4284b63c17d40b3a021892eaf707
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 84564a8..5cd8f67 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -15,16 +15,13 @@
 import {applyPatches, Patch} from 'immer';
 
 import {assertExists} from '../base/logging';
-import {DeferredAction} from '../common/actions';
 import {createEmptyState} from '../common/empty_state';
 import {State} from '../common/state';
-import {globals as frontendGlobals} from '../frontend/globals';
 
 import {ControllerAny} from './controller';
 
 export interface App {
   state: State;
-  dispatch(action: DeferredAction): void;
 }
 
 /**
@@ -40,19 +37,6 @@
     this._state = createEmptyState();
   }
 
-  dispatch(action: DeferredAction): void {
-    frontendGlobals.dispatch(action);
-  }
-
-  // Send the passed dispatch actions to the frontend. The frontend logic
-  // will run the actions, compute the new state and invoke patchState() so
-  // our copy is updated.
-  dispatchMultiple(actions: DeferredAction[]): void {
-    for (const action of actions) {
-      this.dispatch(action);
-    }
-  }
-
   // This is called by the frontend logic which now owns and handle the
   // source-of-truth state, to give us an update on the newer state updates.
   patchState(patches: Patch[]): void {
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
index d0e0916..7c7c9be 100644
--- a/ui/src/controller/metrics_controller.ts
+++ b/ui/src/controller/metrics_controller.ts
@@ -15,6 +15,7 @@
 import {Actions} from '../common/actions';
 import {Engine} from '../common/engine';
 import {QueryError} from '../common/query_result';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {publishMetricResult} from '../frontend/publish';
 
 import {Controller} from './controller';
@@ -48,7 +49,7 @@
         throw e;
       }
     }
-    globals.dispatch(Actions.resetMetricRequest({name}));
+    frontendGlobals.dispatch(Actions.resetMetricRequest({name}));
     this.currentlyRunningMetric = undefined;
   }
 
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index 349a02a..22cf3de 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -79,7 +79,7 @@
 
       PermalinkController.createPermalink(isRecordingConfig)
           .then((hash) => {
-            globals.dispatch(Actions.setPermalink({requestId, hash}));
+            frontendGlobals.dispatch(Actions.setPermalink({requestId, hash}));
           })
           .finally(() => {
             publishConversionJobStatusUpdate({
@@ -99,11 +99,12 @@
             const validConfig =
                 runValidator(recordConfigValidator, stateOrConfig as unknown)
                     .result;
-            globals.dispatch(Actions.setRecordConfig({config: validConfig}));
+            frontendGlobals.dispatch(
+                Actions.setRecordConfig({config: validConfig}));
             Router.navigate('#!/record');
             return;
           }
-          globals.dispatch(Actions.setState({newState: stateOrConfig}));
+          frontendGlobals.dispatch(Actions.setState({newState: stateOrConfig}));
           this.lastRequestId = stateOrConfig.permalink.requestId;
         });
   }
@@ -215,7 +216,7 @@
 
   private static updateStatus(msg: string): void {
     // TODO(hjd): Unify loading updates.
-    globals.dispatch(Actions.updateStatus({
+    frontendGlobals.dispatch(Actions.updateStatus({
       msg,
       timestamp: Date.now() / 1000,
     }));
diff --git a/ui/src/controller/pivot_table_redux_controller.ts b/ui/src/controller/pivot_table_redux_controller.ts
index 4f57891..8658822 100644
--- a/ui/src/controller/pivot_table_redux_controller.ts
+++ b/ui/src/controller/pivot_table_redux_controller.ts
@@ -26,6 +26,7 @@
   PivotTableReduxResult,
   PivotTableReduxState,
 } from '../common/state';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {
   aggregationIndex,
   generateQueryFromState,
@@ -219,7 +220,7 @@
     if (!it.valid()) {
       // Iterator is invalid after creation; means that there are no rows
       // satisfying filtering criteria. Return an empty tree.
-      globals.dispatch(Actions.setPivotStateQueryResult(
+      frontendGlobals.dispatch(Actions.setPivotStateQueryResult(
           {queryResult: createEmptyQueryResult(query.metadata)}));
       return;
     }
@@ -232,9 +233,9 @@
       treeBuilder.ingestRow(nextRow());
     }
 
-    globals.dispatch(Actions.setPivotStateQueryResult(
+    frontendGlobals.dispatch(Actions.setPivotStateQueryResult(
         {queryResult: {tree: treeBuilder.build(), metadata: query.metadata}}));
-    globals.dispatch(Actions.setCurrentTab({tab: 'pivot_table_redux'}));
+    frontendGlobals.dispatch(Actions.setCurrentTab({tab: 'pivot_table_redux'}));
   }
 
   async requestArgumentNames() {
@@ -250,7 +251,8 @@
       it.next();
     }
 
-    globals.dispatch(Actions.setPivotTableArgumentNames({argumentNames}));
+    frontendGlobals.dispatch(
+        Actions.setPivotTableArgumentNames({argumentNames}));
   }
 
 
@@ -269,17 +271,18 @@
     if (pivotTableState.queryRequested ||
         (selection !== null && selection.kind === 'AREA' &&
          this.shouldRerun(pivotTableState, selection))) {
-      globals.dispatch(
+      frontendGlobals.dispatch(
           Actions.setPivotTableQueryRequested({queryRequested: false}));
       // Need to re-run the existing query, clear the current result.
-      globals.dispatch(Actions.setPivotStateQueryResult({queryResult: null}));
+      frontendGlobals.dispatch(
+          Actions.setPivotStateQueryResult({queryResult: null}));
       this.processQuery(generateQueryFromState(pivotTableState));
     }
 
     if (selection !== null && selection.kind === 'AREA' &&
         (pivotTableState.selectionArea === undefined ||
          pivotTableState.selectionArea.areaId !== selection.areaId)) {
-      globals.dispatch(
+      frontendGlobals.dispatch(
           Actions.togglePivotTableRedux({areaId: selection.areaId}));
     }
   }
diff --git a/ui/src/controller/query_controller.ts b/ui/src/controller/query_controller.ts
index 4e9017f..3a896d8 100644
--- a/ui/src/controller/query_controller.ts
+++ b/ui/src/controller/query_controller.ts
@@ -16,6 +16,7 @@
 import {Actions} from '../common/actions';
 import {Engine} from '../common/engine';
 import {runQuery} from '../common/queries';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {publishQueryResult} from '../frontend/publish';
 
 import {Controller} from './controller';
@@ -39,7 +40,7 @@
             .then((result) => {
               console.log(`Query ${config.query} took ${result.durationMs} ms`);
               publishQueryResult({id: this.args.queryId, data: result});
-              globals.dispatch(
+              frontendGlobals.dispatch(
                   Actions.deleteQuery({queryId: this.args.queryId}));
             });
         this.setState('querying');
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index aef43ab..3ce567e 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -29,6 +29,7 @@
   isChromeTarget,
   RecordingTarget,
 } from '../common/state';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {publishBufferUsage, publishTrackData} from '../frontend/publish';
 
 import {AdbOverWebUsb} from './adb';
@@ -208,7 +209,8 @@
       if (this.app.state.extensionInstalled) {
         this.extensionPort.postMessage({method: 'GetCategories'});
       }
-      globals.dispatch(Actions.setFetchChromeCategories({fetch: false}));
+      frontendGlobals.dispatch(
+          Actions.setFetchChromeCategories({fetch: false}));
     }
     if (this.app.state.recordConfig === this.config &&
         this.app.state.recordingInProgress === this.recordingInProgress) {
@@ -302,15 +304,15 @@
 
   onTraceComplete() {
     this.consumerPort.freeBuffers({});
-    globals.dispatch(Actions.setRecordingStatus({status: undefined}));
+    frontendGlobals.dispatch(Actions.setRecordingStatus({status: undefined}));
     if (globals.state.recordingCancelled) {
-      globals.dispatch(
+      frontendGlobals.dispatch(
           Actions.setLastRecordingError({error: 'Recording cancelled.'}));
       this.traceBuffer = [];
       return;
     }
     const trace = this.generateTrace();
-    globals.dispatch(Actions.openTraceFromBuffer({
+    frontendGlobals.dispatch(Actions.openTraceFromBuffer({
       title: 'Recorded trace',
       buffer: trace.buffer,
       fileName: `recorded_trace${this.recordedTraceSuffix}`,
@@ -346,13 +348,13 @@
   onError(message: string) {
     // TODO(octaviant): b/204998302
     console.error('Error in record controller: ', message);
-    globals.dispatch(
+    frontendGlobals.dispatch(
         Actions.setLastRecordingError({error: message.substr(0, 150)}));
-    globals.dispatch(Actions.stopRecording({}));
+    frontendGlobals.dispatch(Actions.stopRecording({}));
   }
 
   onStatus(message: string) {
-    globals.dispatch(Actions.setRecordingStatus({status: message}));
+    frontendGlobals.dispatch(Actions.setRecordingStatus({status: message}));
   }
 
   // Depending on the recording target, different implementation of the
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index dd62689..a6c8771 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -206,17 +206,17 @@
     switch (this.state) {
       case 'init':
         this.loadTrace()
-          .then((mode) => {
-            globals.dispatch(Actions.setEngineReady({
-              engineId: this.engineId,
-              ready: true,
-              mode,
-            }));
-          })
-          .catch((err) => {
-            this.updateStatus(`${err}`);
-            throw err;
-          });
+            .then((mode) => {
+              frontendGlobals.dispatch(Actions.setEngineReady({
+                engineId: this.engineId,
+                ready: true,
+                mode,
+              }));
+            })
+            .catch((err) => {
+              this.updateStatus(`${err}`);
+              throw err;
+            });
         this.updateStatus('Opening trace');
         this.setState('loading_trace');
         break;
@@ -349,8 +349,8 @@
       engineMode = 'HTTP_RPC';
       engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance);
       engine.errorHandler = (err) => {
-        globals.dispatch(
-          Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`}));
+        frontendGlobals.dispatch(
+            Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`}));
         throw err;
       };
     } else {
@@ -375,7 +375,7 @@
       new BottomTabList(engine.getProxy('BottomTabList'));
 
     frontendGlobals.engines.set(this.engineId, engine);
-    globals.dispatch(Actions.setEngineReady({
+    frontendGlobals.dispatch(Actions.setEngineReady({
       engineId: this.engineId,
       ready: false,
       mode: engineMode,
@@ -476,7 +476,7 @@
       resolution,
     }));
 
-    globals.dispatchMultiple(actions);
+    frontendGlobals.dispatchMultiple(actions);
     Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`);
 
     // Make sure the helper views are available before we start adding tracks.
@@ -510,9 +510,9 @@
       publishHasFtrace(hasFtrace);
     }
 
-    globals.dispatch(Actions.removeDebugTrack({}));
-    globals.dispatch(Actions.sortThreadTracks({}));
-    globals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
+    frontendGlobals.dispatch(Actions.removeDebugTrack({}));
+    frontendGlobals.dispatch(Actions.sortThreadTracks({}));
+    frontendGlobals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
 
     await this.selectFirstHeapProfile();
     if (PERF_SAMPLE_FLAG.get()) {
@@ -531,7 +531,7 @@
     if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()) {
       const reliableRangeStart = await computeTraceReliableRangeStart(engine);
       if (reliableRangeStart > 0) {
-        globals.dispatch(Actions.addAutomaticNote({
+        frontendGlobals.dispatch(Actions.addAutomaticNote({
           timestamp: reliableRangeStart,
           color: '#ff0000',
           text: 'Reliable Range Start',
@@ -554,8 +554,8 @@
     const upid = row.upid;
     const leftTs = toNs(globals.state.traceTime.startSec);
     const rightTs = toNs(globals.state.traceTime.endSec);
-    globals.dispatch(Actions.selectPerfSamples(
-      {id: 0, upid, leftTs, rightTs, type: ProfileType.PERF_SAMPLE}));
+    frontendGlobals.dispatch(Actions.selectPerfSamples(
+        {id: 0, upid, leftTs, rightTs, type: ProfileType.PERF_SAMPLE}));
   }
 
   private async selectFirstHeapProfile() {
@@ -576,14 +576,15 @@
     const ts = row.ts;
     const type = profileType(row.type);
     const upid = row.upid;
-    globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
+    frontendGlobals.dispatch(
+        Actions.selectHeapProfile({id: 0, upid, ts, type}));
   }
 
   private async listTracks() {
     this.updateStatus('Loading tracks');
     const engine = assertExists<Engine>(this.engine);
     const actions = await decideTracks(this.engineId, engine);
-    globals.dispatchMultiple(actions);
+    frontendGlobals.dispatchMultiple(actions);
   }
 
   private async listThreads() {
@@ -776,7 +777,7 @@
     for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) {
       availableMetrics.push(it.name);
     }
-    globals.dispatch(Actions.setAvailableMetrics({availableMetrics}));
+    frontendGlobals.dispatch(Actions.setAvailableMetrics({availableMetrics}));
 
     const availableMetricsSet = new Set<string>(availableMetrics);
     for (const [flag, metric] of FLAGGED_METRICS) {
@@ -895,7 +896,7 @@
   }
 
   private updateStatus(msg: string): void {
-    globals.dispatch(Actions.updateStatus({
+    frontendGlobals.dispatch(Actions.updateStatus({
       msg,
       timestamp: Date.now() / 1000,
     }));
diff --git a/ui/src/controller/visualised_args_controller.ts b/ui/src/controller/visualised_args_controller.ts
index 9ac6d3d..ec1d67c 100644
--- a/ui/src/controller/visualised_args_controller.ts
+++ b/ui/src/controller/visualised_args_controller.ts
@@ -18,6 +18,7 @@
 import {Engine} from '../common/engine';
 import {NUM} from '../common/query_result';
 import {InThreadTrackSortKey} from '../common/state';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {
   VISUALISED_ARGS_SLICE_TRACK_KIND,
 } from '../tracks/visualised_args/index';
@@ -48,7 +49,7 @@
 
   onDestroy() {
     this.engine.query(`drop table if exists ${this.tableName}`);
-    globals.dispatch(
+    frontendGlobals.dispatch(
         Actions.removeVisualisedArgTracks({trackIds: this.addedTrackIds}));
   }
 
@@ -113,8 +114,8 @@
         },
       });
     }
-    globals.dispatch(Actions.addTracks({tracks: tracksToAdd}));
-    globals.dispatch(Actions.sortThreadTracks({}));
+    frontendGlobals.dispatch(Actions.addTracks({tracks: tracksToAdd}));
+    frontendGlobals.dispatch(Actions.sortThreadTracks({}));
   }
 
   run() {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 5b8728a..0e5de55 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -293,6 +293,13 @@
     return assertExists(this._dispatch);
   }
 
+  dispatchMultiple(actions: DeferredAction[]): void {
+    const dispatch = this.dispatch;
+    for (const action of actions) {
+      dispatch(action);
+    }
+  }
+
   get frontendLocalState() {
     return assertExists(this._frontendLocalState);
   }