Merge "Roll clang and switch to fsanitize=fuzzer-no-link"
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 9da59d9..bd7cada 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -20,7 +20,6 @@
 export function openTraceFromUrl(url: string) {
   return {
     type: 'OPEN_TRACE_FROM_URL',
-    id: new Date().toISOString(),
     url,
   };
 }
@@ -28,7 +27,6 @@
 export function openTraceFromFile(file: File) {
   return {
     type: 'OPEN_TRACE_FROM_FILE',
-    id: new Date().toISOString(),
     file,
   };
 }
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index c3c9e21..291a865 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -80,13 +80,19 @@
   status: Status;
 }
 
+export const defaultTraceTime = {
+  startSec: 0,
+  endSec: 10,
+  lastUpdate: 0
+};
+
 export function createEmptyState(): State {
   return {
     route: null,
     nextId: 0,
     engines: {},
-    traceTime: {startSec: 0, endSec: 10, lastUpdate: 0},
-    visibleTraceTime: {startSec: 0, endSec: 10, lastUpdate: 0},
+    traceTime: {...defaultTraceTime},
+    visibleTraceTime: {...defaultTraceTime},
     tracks: {},
     pinnedTracks: [],
     scrollingTracks: [],
diff --git a/ui/src/controller/app_controller.ts b/ui/src/controller/app_controller.ts
index 9d67735..3a83f18 100644
--- a/ui/src/controller/app_controller.ts
+++ b/ui/src/controller/app_controller.ts
@@ -31,13 +31,11 @@
   // - An internal promise of a nested controller being resolved and manually
   //   re-triggering the controllers.
   run() {
-    const engineKeys = Object.keys(globals.state.engines);
     const childControllers: ControllerInitializerAny[] = [
       Child('permalink', PermalinkController, {}),
     ];
-    if (engineKeys.length > 0) {
-      const cfg = globals.state.engines[engineKeys[0]];
-      childControllers.push(Child(cfg.id, TraceController, cfg.id));
+    for (const engineCfg of Object.values(globals.state.engines)) {
+      childControllers.push(Child(engineCfg.id, TraceController, engineCfg.id));
     }
     return childControllers;
   }
diff --git a/ui/src/controller/reducer.ts b/ui/src/controller/reducer.ts
index a969bc0..8e6cc9b 100644
--- a/ui/src/controller/reducer.ts
+++ b/ui/src/controller/reducer.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {createEmptyState, State} from '../common/state';
+import {defaultTraceTime, State} from '../common/state';
 
 // TODO(hjd): Type check this better.
 // tslint:disable-next-line no-any
@@ -25,9 +25,15 @@
     }
 
     case 'OPEN_TRACE_FROM_FILE': {
-      const nextState = createEmptyState();
-      nextState.engines[action.id] = {
-        id: action.id,
+      const nextState = {...state};
+      nextState.traceTime = {...defaultTraceTime};
+      nextState.visibleTraceTime = {...defaultTraceTime};
+      const id = `${nextState.nextId++}`;
+      // Reset displayed tracks.
+      nextState.pinnedTracks = [];
+      nextState.scrollingTracks = [];
+      nextState.engines[id] = {
+        id,
         ready: false,
         source: action.file,
       };
@@ -37,9 +43,15 @@
     }
 
     case 'OPEN_TRACE_FROM_URL': {
-      const nextState = createEmptyState();
-      nextState.engines[action.id] = {
-        id: action.id,
+      const nextState = {...state};
+      nextState.traceTime = {...defaultTraceTime};
+      nextState.visibleTraceTime = {...defaultTraceTime};
+      const id = `${nextState.nextId++}`;
+      // Reset displayed tracks.
+      nextState.pinnedTracks = [];
+      nextState.scrollingTracks = [];
+      nextState.engines[id] = {
+        id,
         ready: false,
         source: action.url,
       };
diff --git a/ui/src/controller/reducer_unittest.ts b/ui/src/controller/reducer_unittest.ts
index 4abf173..fc0f246 100644
--- a/ui/src/controller/reducer_unittest.ts
+++ b/ui/src/controller/reducer_unittest.ts
@@ -194,3 +194,24 @@
   });
   expect(after).toBe(newState);
 });
+
+test('open second trace from file', () => {
+  const before = createEmptyState();
+  const afterFirst = rootReducer(before, {
+    type: 'OPEN_TRACE_FROM_URL',
+    url: 'https://example.com/bar',
+  });
+  afterFirst.scrollingTracks = ['track1', 'track2'];
+  afterFirst.pinnedTracks = ['track3', 'track4'];
+  const after = rootReducer(afterFirst, {
+    type: 'OPEN_TRACE_FROM_URL',
+    url: 'https://example.com/foo',
+  });
+  const engineKeys = Object.keys(after.engines);
+  expect(engineKeys.length).toBe(2);
+  expect(after.engines[engineKeys[0]].source).toBe('https://example.com/bar');
+  expect(after.engines[engineKeys[1]].source).toBe('https://example.com/foo');
+  expect(after.pinnedTracks.length).toBe(0);
+  expect(after.scrollingTracks.length).toBe(0);
+  expect(after.route).toBe('/viewer');
+});