Merge "ui: Backport fix for Track outliving its state"
diff --git a/ui/src/base/remote.ts b/ui/src/base/remote.ts
deleted file mode 100644
index 578bd19..0000000
--- a/ui/src/base/remote.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {defer, Deferred} from './deferred';
-
-interface RemoteResponse {
-  id: number;
-  result: {};
-}
-
-/**
- * A proxy for an object that lives on another thread.
- */
-export class Remote {
-  private nextRequestId: number;
-  private port: MessagePort;
-  // tslint:disable-next-line no-any
-  private deferredRequests: Map<number, Deferred<any>>;
-
-  constructor(port: MessagePort) {
-    this.nextRequestId = 0;
-    this.deferredRequests = new Map();
-    this.port = port;
-    this.port.onmessage = (event: MessageEvent) => {
-      this.receive(event.data);
-    };
-  }
-
-  /**
-   * Invoke method with name |method| with |args| on the remote object.
-   * Optionally set |transferList| to transfer those objects.
-   */
-  // tslint:disable-next-line no-any
-  send<T extends any>(
-      method: string,
-      args: Array<{}>, transferList?: Transferable[]): Promise<T> {
-    const d = defer<T>();
-    this.deferredRequests.set(this.nextRequestId, d);
-    const message = {
-      responseId: this.nextRequestId,
-      method,
-      args,
-    };
-    if (transferList === undefined) {
-      this.port.postMessage(message);
-    } else {
-      this.port.postMessage(message, transferList);
-    }
-    this.nextRequestId += 1;
-    return d;
-  }
-
-  private receive(response: RemoteResponse): void {
-    const d = this.deferredRequests.get(response.id);
-    if (!d) throw new Error(`No deferred response with ID ${response.id}`);
-    this.deferredRequests.delete(response.id);
-    d.resolve(response.result);
-  }
-}
-
-/**
- * Given a MessagePort |port| where the other end is owned by a Remote
- * (see above) turn each incoming MessageEvent into a call on |handler|
- * and post the result back to the calling thread.
- */
-export function forwardRemoteCalls(
-    port: MessagePort,
-    // tslint:disable-next-line no-any
-    handler: {[key: string]: any}) {
-  port.onmessage = (msg: MessageEvent) => {
-    const method = msg.data.method;
-    const id = msg.data.responseId;
-    const args = msg.data.args || [];
-    if (method === undefined || id === undefined) {
-      throw new Error(`Invalid call method: ${method} id: ${id}`);
-    }
-
-    if (!(handler[method] instanceof Function)) {
-      throw new Error(`Method not known: ${method}(${args})`);
-    }
-
-    const result = handler[method].apply(handler, args);
-    const transferList = [];
-
-    if (result !== undefined && result.port instanceof MessagePort) {
-      transferList.push(result.port);
-    }
-
-    port.postMessage(
-        {
-          id,
-          result,
-        },
-        transferList);
-  };
-}
diff --git a/ui/src/common/worker_messages.ts b/ui/src/common/worker_messages.ts
index b60a656..b90f6b2 100644
--- a/ui/src/common/worker_messages.ts
+++ b/ui/src/common/worker_messages.ts
@@ -35,10 +35,6 @@
   // the frontend <> controller interaction happens.
   controllerPort: MessagePort;
 
-  // For publishing results back to the frontend. This is used for one-way
-  // non-retained publish() operations (e.g. track data after a query).
-  frontendPort: MessagePort;
-
   // For controller <> Chrome extension communication.
   extensionPort: MessagePort;
 
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index b7784df..f479f37 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -15,9 +15,9 @@
 import {applyPatches, Patch} from 'immer';
 
 import {assertExists} from '../base/logging';
-import {Remote} from '../base/remote';
 import {DeferredAction} from '../common/actions';
 import {createEmptyState, State} from '../common/state';
+import {globals as frontendGlobals} from '../frontend/globals';
 import {ControllerAny} from './controller';
 
 export interface App {
@@ -31,24 +31,24 @@
 class Globals implements App {
   private _state?: State;
   private _rootController?: ControllerAny;
-  private _frontend?: Remote;
   private _runningControllers = false;
 
-  initialize(rootController: ControllerAny, frontendProxy: Remote) {
+  initialize(rootController: ControllerAny) {
     this._rootController = rootController;
-    this._frontend = frontendProxy;
     this._state = createEmptyState();
   }
 
   dispatch(action: DeferredAction): void {
-    this.dispatchMultiple([action]);
+    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 {
-    assertExists(this._frontend).send<void>('dispatchMultiple', [actions]);
+    for (const action of actions) {
+      this.dispatch(action);
+    }
   }
 
   // This is called by the frontend logic which now owns and handle the
diff --git a/ui/src/controller/index.ts b/ui/src/controller/index.ts
index a84f84b..5b4bbdd 100644
--- a/ui/src/controller/index.ts
+++ b/ui/src/controller/index.ts
@@ -15,7 +15,6 @@
 import '../gen/all_tracks';
 
 import {assertTrue} from '../base/logging';
-import {Remote} from '../base/remote';
 import {ControllerWorkerInitMessage} from '../common/worker_messages';
 import {AppController} from './app_controller';
 import {globals} from './globals';
@@ -24,12 +23,10 @@
 export function initController(init: ControllerWorkerInitMessage) {
   assertTrue(!initialized);
   initialized = true;
-  const frontendPort = init.frontendPort;
   const controllerPort = init.controllerPort;
   const extensionPort = init.extensionPort;
-  const frontend = new Remote(frontendPort);
   controllerPort.onmessage = ({data}) => globals.patchState(data);
-  globals.initialize(new AppController(extensionPort), frontend);
+  globals.initialize(new AppController(extensionPort));
 }
 
 
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index f3b053c..ab98c79 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -17,7 +17,6 @@
 
 import {defer} from '../base/deferred';
 import {assertExists, reportError, setErrorHandler} from '../base/logging';
-import {forwardRemoteCalls} from '../base/remote';
 import {Actions, DeferredAction, StateActions} from '../common/actions';
 import {initializeImmerJs} from '../common/immer_init';
 import {createEmptyState, State} from '../common/state';
@@ -44,9 +43,6 @@
 
 const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine';
 
-/**
- * The API the main thread exposes to the controller.
- */
 class FrontendApi {
   private port: MessagePort;
   private state: State;
@@ -116,25 +112,6 @@
     return patches;
   }
 
-  patchState(patches: Patch[]) {
-    const oldState = globals.state;
-    globals.state = applyPatches(globals.state, patches);
-
-    // If the visible time in the global state has been updated more recently
-    // than the visible time handled by the frontend @ 60fps, update it. This
-    // typically happens when restoring the state from a permalink.
-    globals.frontendLocalState.mergeState(globals.state.frontendLocalState);
-
-    // Only redraw if something other than the frontendLocalState changed.
-    for (const key in globals.state) {
-      if (key !== 'frontendLocalState' && key !== 'visibleTracks' &&
-          oldState[key] !== globals.state[key]) {
-        globals.rafScheduler.scheduleFullRedraw();
-        return;
-      }
-    }
-  }
-
 }
 
 function setExtensionAvailability(available: boolean) {
@@ -217,7 +194,6 @@
   window.addEventListener('error', e => reportError(e));
   window.addEventListener('unhandledrejection', e => reportError(e));
 
-  const frontendChannel = new MessageChannel();
   const controllerChannel = new MessageChannel();
   const extensionLocalChannel = new MessageChannel();
   const errorReportingChannel = new MessageChannel();
@@ -226,7 +202,6 @@
       maybeShowErrorDialog(`${e.data}`);
 
   const msg: ControllerWorkerInitMessage = {
-    frontendPort: frontendChannel.port1,
     controllerPort: controllerChannel.port1,
     extensionPort: extensionLocalChannel.port1,
     errorReportingPort: errorReportingChannel.port1,
@@ -260,8 +235,6 @@
   const frontendApi = new FrontendApi(controllerChannel.port2);
   globals.publishRedraw = () => globals.rafScheduler.scheduleFullRedraw();
 
-  forwardRemoteCalls(frontendChannel.port2, frontendApi);
-
   // We proxy messages between the extension and the controller because the
   // controller's worker can't access chrome.runtime.
   const extensionPort = window.chrome && chrome.runtime ?