xref: /aosp_15_r20/development/tools/winscope/src/app/mediator.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerimport {assertDefined} from 'common/assert_utils';
18*90c8c64dSAndroid Build Coastguard Workerimport {Store} from 'common/store';
19*90c8c64dSAndroid Build Coastguard Workerimport {Timestamp} from 'common/time';
20*90c8c64dSAndroid Build Coastguard Workerimport {TimeUtils} from 'common/time_utils';
21*90c8c64dSAndroid Build Coastguard Workerimport {UserNotifier} from 'common/user_notifier';
22*90c8c64dSAndroid Build Coastguard Workerimport {CrossToolProtocol} from 'cross_tool/cross_tool_protocol';
23*90c8c64dSAndroid Build Coastguard Workerimport {Analytics} from 'logging/analytics';
24*90c8c64dSAndroid Build Coastguard Workerimport {ProgressListener} from 'messaging/progress_listener';
25*90c8c64dSAndroid Build Coastguard Workerimport {UserWarning} from 'messaging/user_warning';
26*90c8c64dSAndroid Build Coastguard Workerimport {
27*90c8c64dSAndroid Build Coastguard Worker  CannotVisualizeTraceEntry,
28*90c8c64dSAndroid Build Coastguard Worker  FailedToInitializeTimelineData,
29*90c8c64dSAndroid Build Coastguard Worker  IncompleteFrameMapping,
30*90c8c64dSAndroid Build Coastguard Worker  NoTraceTargetsSelected,
31*90c8c64dSAndroid Build Coastguard Worker  NoValidFiles,
32*90c8c64dSAndroid Build Coastguard Worker} from 'messaging/user_warnings';
33*90c8c64dSAndroid Build Coastguard Workerimport {
34*90c8c64dSAndroid Build Coastguard Worker  ActiveTraceChanged,
35*90c8c64dSAndroid Build Coastguard Worker  ExpandedTimelineToggled,
36*90c8c64dSAndroid Build Coastguard Worker  TraceAddRequest,
37*90c8c64dSAndroid Build Coastguard Worker  TracePositionUpdate,
38*90c8c64dSAndroid Build Coastguard Worker  TraceSearchCompleted,
39*90c8c64dSAndroid Build Coastguard Worker  TraceSearchFailed,
40*90c8c64dSAndroid Build Coastguard Worker  TraceSearchInitialized,
41*90c8c64dSAndroid Build Coastguard Worker  ViewersLoaded,
42*90c8c64dSAndroid Build Coastguard Worker  ViewersUnloaded,
43*90c8c64dSAndroid Build Coastguard Worker  WinscopeEvent,
44*90c8c64dSAndroid Build Coastguard Worker  WinscopeEventType,
45*90c8c64dSAndroid Build Coastguard Worker} from 'messaging/winscope_event';
46*90c8c64dSAndroid Build Coastguard Workerimport {WinscopeEventEmitter} from 'messaging/winscope_event_emitter';
47*90c8c64dSAndroid Build Coastguard Workerimport {WinscopeEventListener} from 'messaging/winscope_event_listener';
48*90c8c64dSAndroid Build Coastguard Workerimport {TraceEntry} from 'trace/trace';
49*90c8c64dSAndroid Build Coastguard Workerimport {TRACE_INFO} from 'trace/trace_info';
50*90c8c64dSAndroid Build Coastguard Workerimport {TracePosition} from 'trace/trace_position';
51*90c8c64dSAndroid Build Coastguard Workerimport {TraceType} from 'trace/trace_type';
52*90c8c64dSAndroid Build Coastguard Workerimport {RequestedTraceTypes} from 'trace_collection/adb_files';
53*90c8c64dSAndroid Build Coastguard Workerimport {View, Viewer, ViewType} from 'viewers/viewer';
54*90c8c64dSAndroid Build Coastguard Workerimport {ViewerFactory} from 'viewers/viewer_factory';
55*90c8c64dSAndroid Build Coastguard Workerimport {FilesSource} from './files_source';
56*90c8c64dSAndroid Build Coastguard Workerimport {TimelineData} from './timeline_data';
57*90c8c64dSAndroid Build Coastguard Workerimport {TracePipeline} from './trace_pipeline';
58*90c8c64dSAndroid Build Coastguard Workerimport {TraceSearchInitializer} from './trace_search/trace_search_initializer';
59*90c8c64dSAndroid Build Coastguard Worker
60*90c8c64dSAndroid Build Coastguard Workerexport class Mediator {
61*90c8c64dSAndroid Build Coastguard Worker  private abtChromeExtensionProtocol: WinscopeEventEmitter &
62*90c8c64dSAndroid Build Coastguard Worker    WinscopeEventListener;
63*90c8c64dSAndroid Build Coastguard Worker  private crossToolProtocol: CrossToolProtocol;
64*90c8c64dSAndroid Build Coastguard Worker  private uploadTracesComponent?: ProgressListener;
65*90c8c64dSAndroid Build Coastguard Worker  private collectTracesComponent?: ProgressListener &
66*90c8c64dSAndroid Build Coastguard Worker    WinscopeEventEmitter &
67*90c8c64dSAndroid Build Coastguard Worker    WinscopeEventListener;
68*90c8c64dSAndroid Build Coastguard Worker  private traceViewComponent?: WinscopeEventEmitter & WinscopeEventListener;
69*90c8c64dSAndroid Build Coastguard Worker  private timelineComponent?: WinscopeEventEmitter & WinscopeEventListener;
70*90c8c64dSAndroid Build Coastguard Worker  private appComponent: WinscopeEventListener;
71*90c8c64dSAndroid Build Coastguard Worker  private storage: Store;
72*90c8c64dSAndroid Build Coastguard Worker
73*90c8c64dSAndroid Build Coastguard Worker  private tracePipeline: TracePipeline;
74*90c8c64dSAndroid Build Coastguard Worker  private timelineData: TimelineData;
75*90c8c64dSAndroid Build Coastguard Worker  private viewers: Viewer[] = [];
76*90c8c64dSAndroid Build Coastguard Worker  private focusedTabView: undefined | View;
77*90c8c64dSAndroid Build Coastguard Worker  private areViewersLoaded = false;
78*90c8c64dSAndroid Build Coastguard Worker  private lastRemoteToolDeferredTimestampReceived?: () => Timestamp | undefined;
79*90c8c64dSAndroid Build Coastguard Worker  private currentProgressListener?: ProgressListener;
80*90c8c64dSAndroid Build Coastguard Worker
81*90c8c64dSAndroid Build Coastguard Worker  constructor(
82*90c8c64dSAndroid Build Coastguard Worker    tracePipeline: TracePipeline,
83*90c8c64dSAndroid Build Coastguard Worker    timelineData: TimelineData,
84*90c8c64dSAndroid Build Coastguard Worker    abtChromeExtensionProtocol: WinscopeEventEmitter & WinscopeEventListener,
85*90c8c64dSAndroid Build Coastguard Worker    crossToolProtocol: CrossToolProtocol,
86*90c8c64dSAndroid Build Coastguard Worker    appComponent: WinscopeEventListener,
87*90c8c64dSAndroid Build Coastguard Worker    storage: Store,
88*90c8c64dSAndroid Build Coastguard Worker  ) {
89*90c8c64dSAndroid Build Coastguard Worker    this.tracePipeline = tracePipeline;
90*90c8c64dSAndroid Build Coastguard Worker    this.timelineData = timelineData;
91*90c8c64dSAndroid Build Coastguard Worker    this.abtChromeExtensionProtocol = abtChromeExtensionProtocol;
92*90c8c64dSAndroid Build Coastguard Worker    this.crossToolProtocol = crossToolProtocol;
93*90c8c64dSAndroid Build Coastguard Worker    this.appComponent = appComponent;
94*90c8c64dSAndroid Build Coastguard Worker    this.storage = storage;
95*90c8c64dSAndroid Build Coastguard Worker
96*90c8c64dSAndroid Build Coastguard Worker    this.crossToolProtocol.setEmitEvent(async (event) => {
97*90c8c64dSAndroid Build Coastguard Worker      await this.onWinscopeEvent(event);
98*90c8c64dSAndroid Build Coastguard Worker    });
99*90c8c64dSAndroid Build Coastguard Worker
100*90c8c64dSAndroid Build Coastguard Worker    this.abtChromeExtensionProtocol.setEmitEvent(async (event) => {
101*90c8c64dSAndroid Build Coastguard Worker      await this.onWinscopeEvent(event);
102*90c8c64dSAndroid Build Coastguard Worker    });
103*90c8c64dSAndroid Build Coastguard Worker  }
104*90c8c64dSAndroid Build Coastguard Worker
105*90c8c64dSAndroid Build Coastguard Worker  setUploadTracesComponent(component: ProgressListener | undefined) {
106*90c8c64dSAndroid Build Coastguard Worker    this.uploadTracesComponent = component;
107*90c8c64dSAndroid Build Coastguard Worker  }
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Worker  setCollectTracesComponent(
110*90c8c64dSAndroid Build Coastguard Worker    component:
111*90c8c64dSAndroid Build Coastguard Worker      | (ProgressListener & WinscopeEventEmitter & WinscopeEventListener)
112*90c8c64dSAndroid Build Coastguard Worker      | undefined,
113*90c8c64dSAndroid Build Coastguard Worker  ) {
114*90c8c64dSAndroid Build Coastguard Worker    this.collectTracesComponent = component;
115*90c8c64dSAndroid Build Coastguard Worker    this.collectTracesComponent?.setEmitEvent(async (event) => {
116*90c8c64dSAndroid Build Coastguard Worker      await this.onWinscopeEvent(event);
117*90c8c64dSAndroid Build Coastguard Worker    });
118*90c8c64dSAndroid Build Coastguard Worker  }
119*90c8c64dSAndroid Build Coastguard Worker
120*90c8c64dSAndroid Build Coastguard Worker  setTraceViewComponent(
121*90c8c64dSAndroid Build Coastguard Worker    component: (WinscopeEventEmitter & WinscopeEventListener) | undefined,
122*90c8c64dSAndroid Build Coastguard Worker  ) {
123*90c8c64dSAndroid Build Coastguard Worker    this.traceViewComponent = component;
124*90c8c64dSAndroid Build Coastguard Worker    this.traceViewComponent?.setEmitEvent(async (event) => {
125*90c8c64dSAndroid Build Coastguard Worker      await this.onWinscopeEvent(event);
126*90c8c64dSAndroid Build Coastguard Worker    });
127*90c8c64dSAndroid Build Coastguard Worker  }
128*90c8c64dSAndroid Build Coastguard Worker
129*90c8c64dSAndroid Build Coastguard Worker  setTimelineComponent(
130*90c8c64dSAndroid Build Coastguard Worker    component: (WinscopeEventEmitter & WinscopeEventListener) | undefined,
131*90c8c64dSAndroid Build Coastguard Worker  ) {
132*90c8c64dSAndroid Build Coastguard Worker    this.timelineComponent = component;
133*90c8c64dSAndroid Build Coastguard Worker    this.timelineComponent?.setEmitEvent(async (event) => {
134*90c8c64dSAndroid Build Coastguard Worker      await this.onWinscopeEvent(event);
135*90c8c64dSAndroid Build Coastguard Worker    });
136*90c8c64dSAndroid Build Coastguard Worker  }
137*90c8c64dSAndroid Build Coastguard Worker
138*90c8c64dSAndroid Build Coastguard Worker  async onWinscopeEvent(event: WinscopeEvent) {
139*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.APP_INITIALIZED, async (event) => {
140*90c8c64dSAndroid Build Coastguard Worker      await this.abtChromeExtensionProtocol.onWinscopeEvent(event);
141*90c8c64dSAndroid Build Coastguard Worker    });
142*90c8c64dSAndroid Build Coastguard Worker
143*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.APP_FILES_UPLOADED, async (event) => {
144*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener = this.uploadTracesComponent;
145*90c8c64dSAndroid Build Coastguard Worker      await this.loadFiles(event.files, FilesSource.UPLOADED);
146*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.notify();
147*90c8c64dSAndroid Build Coastguard Worker    });
148*90c8c64dSAndroid Build Coastguard Worker
149*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.APP_FILES_COLLECTED, async (event) => {
150*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener = this.collectTracesComponent;
151*90c8c64dSAndroid Build Coastguard Worker      if (event.files.collected.length > 0) {
152*90c8c64dSAndroid Build Coastguard Worker        await this.loadFiles(event.files.collected, FilesSource.COLLECTED);
153*90c8c64dSAndroid Build Coastguard Worker        const traces = this.tracePipeline.getTraces();
154*90c8c64dSAndroid Build Coastguard Worker        if (traces.getSize() > 0) {
155*90c8c64dSAndroid Build Coastguard Worker          const failedTraces: string[] = [];
156*90c8c64dSAndroid Build Coastguard Worker          event.files.requested.forEach((requested: RequestedTraceTypes) => {
157*90c8c64dSAndroid Build Coastguard Worker            if (
158*90c8c64dSAndroid Build Coastguard Worker              !requested.types.some((type) => traces.getTraces(type).length > 0)
159*90c8c64dSAndroid Build Coastguard Worker            ) {
160*90c8c64dSAndroid Build Coastguard Worker              failedTraces.push(requested.name);
161*90c8c64dSAndroid Build Coastguard Worker            }
162*90c8c64dSAndroid Build Coastguard Worker          });
163*90c8c64dSAndroid Build Coastguard Worker          if (failedTraces.length > 0) {
164*90c8c64dSAndroid Build Coastguard Worker            UserNotifier.add(new NoValidFiles(failedTraces));
165*90c8c64dSAndroid Build Coastguard Worker          }
166*90c8c64dSAndroid Build Coastguard Worker          await this.loadViewers();
167*90c8c64dSAndroid Build Coastguard Worker        } else {
168*90c8c64dSAndroid Build Coastguard Worker          this.currentProgressListener?.onOperationFinished(false);
169*90c8c64dSAndroid Build Coastguard Worker        }
170*90c8c64dSAndroid Build Coastguard Worker      } else {
171*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.add(new NoValidFiles());
172*90c8c64dSAndroid Build Coastguard Worker      }
173*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.notify();
174*90c8c64dSAndroid Build Coastguard Worker    });
175*90c8c64dSAndroid Build Coastguard Worker
176*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.APP_RESET_REQUEST, async () => {
177*90c8c64dSAndroid Build Coastguard Worker      await this.resetAppToInitialState();
178*90c8c64dSAndroid Build Coastguard Worker    });
179*90c8c64dSAndroid Build Coastguard Worker
180*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
181*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.APP_REFRESH_DUMPS_REQUEST,
182*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
183*90c8c64dSAndroid Build Coastguard Worker        await this.resetAppToInitialState();
184*90c8c64dSAndroid Build Coastguard Worker        await this.collectTracesComponent?.onWinscopeEvent(event);
185*90c8c64dSAndroid Build Coastguard Worker      },
186*90c8c64dSAndroid Build Coastguard Worker    );
187*90c8c64dSAndroid Build Coastguard Worker
188*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.APP_TRACE_VIEW_REQUEST, async () => {
189*90c8c64dSAndroid Build Coastguard Worker      await this.loadViewers();
190*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.notify();
191*90c8c64dSAndroid Build Coastguard Worker    });
192*90c8c64dSAndroid Build Coastguard Worker
193*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
194*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.REMOTE_TOOL_DOWNLOAD_START,
195*90c8c64dSAndroid Build Coastguard Worker      async () => {
196*90c8c64dSAndroid Build Coastguard Worker        Analytics.Tracing.logOpenFromABT();
197*90c8c64dSAndroid Build Coastguard Worker        await this.resetAppToInitialState();
198*90c8c64dSAndroid Build Coastguard Worker        this.currentProgressListener = this.uploadTracesComponent;
199*90c8c64dSAndroid Build Coastguard Worker        this.currentProgressListener?.onProgressUpdate(
200*90c8c64dSAndroid Build Coastguard Worker          'Downloading files...',
201*90c8c64dSAndroid Build Coastguard Worker          undefined,
202*90c8c64dSAndroid Build Coastguard Worker        );
203*90c8c64dSAndroid Build Coastguard Worker      },
204*90c8c64dSAndroid Build Coastguard Worker    );
205*90c8c64dSAndroid Build Coastguard Worker
206*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
207*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.REMOTE_TOOL_FILES_RECEIVED,
208*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
209*90c8c64dSAndroid Build Coastguard Worker        await this.processRemoteFilesReceived(
210*90c8c64dSAndroid Build Coastguard Worker          event.files,
211*90c8c64dSAndroid Build Coastguard Worker          FilesSource.REMOTE_TOOL,
212*90c8c64dSAndroid Build Coastguard Worker        );
213*90c8c64dSAndroid Build Coastguard Worker        if (event.deferredTimestamp) {
214*90c8c64dSAndroid Build Coastguard Worker          await this.processRemoteToolDeferredTimestampReceived(
215*90c8c64dSAndroid Build Coastguard Worker            event.deferredTimestamp,
216*90c8c64dSAndroid Build Coastguard Worker          );
217*90c8c64dSAndroid Build Coastguard Worker        }
218*90c8c64dSAndroid Build Coastguard Worker      },
219*90c8c64dSAndroid Build Coastguard Worker    );
220*90c8c64dSAndroid Build Coastguard Worker
221*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
222*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.REMOTE_TOOL_TIMESTAMP_RECEIVED,
223*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
224*90c8c64dSAndroid Build Coastguard Worker        await this.processRemoteToolDeferredTimestampReceived(
225*90c8c64dSAndroid Build Coastguard Worker          event.deferredTimestamp,
226*90c8c64dSAndroid Build Coastguard Worker        );
227*90c8c64dSAndroid Build Coastguard Worker      },
228*90c8c64dSAndroid Build Coastguard Worker    );
229*90c8c64dSAndroid Build Coastguard Worker
230*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
231*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.TABBED_VIEW_SWITCH_REQUEST,
232*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
233*90c8c64dSAndroid Build Coastguard Worker        await this.traceViewComponent?.onWinscopeEvent(event);
234*90c8c64dSAndroid Build Coastguard Worker      },
235*90c8c64dSAndroid Build Coastguard Worker    );
236*90c8c64dSAndroid Build Coastguard Worker
237*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.TABBED_VIEW_SWITCHED, async (event) => {
238*90c8c64dSAndroid Build Coastguard Worker      const newActiveTrace = event.newFocusedView.traces[0];
239*90c8c64dSAndroid Build Coastguard Worker      if (this.timelineData.trySetActiveTrace(newActiveTrace)) {
240*90c8c64dSAndroid Build Coastguard Worker        const activeTraceChanged = new ActiveTraceChanged(newActiveTrace);
241*90c8c64dSAndroid Build Coastguard Worker        await this.timelineComponent?.onWinscopeEvent(activeTraceChanged);
242*90c8c64dSAndroid Build Coastguard Worker        for (const viewer of this.viewers) {
243*90c8c64dSAndroid Build Coastguard Worker          await viewer.onWinscopeEvent(activeTraceChanged);
244*90c8c64dSAndroid Build Coastguard Worker        }
245*90c8c64dSAndroid Build Coastguard Worker      }
246*90c8c64dSAndroid Build Coastguard Worker      this.focusedTabView = event.newFocusedView;
247*90c8c64dSAndroid Build Coastguard Worker      await this.propagateTracePosition(
248*90c8c64dSAndroid Build Coastguard Worker        this.timelineData.getCurrentPosition(),
249*90c8c64dSAndroid Build Coastguard Worker        false,
250*90c8c64dSAndroid Build Coastguard Worker      );
251*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.notify();
252*90c8c64dSAndroid Build Coastguard Worker    });
253*90c8c64dSAndroid Build Coastguard Worker
254*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
255*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.TRACE_POSITION_UPDATE,
256*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
257*90c8c64dSAndroid Build Coastguard Worker        if (event.updateTimeline) {
258*90c8c64dSAndroid Build Coastguard Worker          this.timelineData.setPosition(event.position);
259*90c8c64dSAndroid Build Coastguard Worker        }
260*90c8c64dSAndroid Build Coastguard Worker        await this.propagateTracePosition(event.position, false);
261*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.notify();
262*90c8c64dSAndroid Build Coastguard Worker      },
263*90c8c64dSAndroid Build Coastguard Worker    );
264*90c8c64dSAndroid Build Coastguard Worker
265*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
266*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.EXPANDED_TIMELINE_TOGGLED,
267*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
268*90c8c64dSAndroid Build Coastguard Worker        await this.propagateToOverlays(event);
269*90c8c64dSAndroid Build Coastguard Worker      },
270*90c8c64dSAndroid Build Coastguard Worker    );
271*90c8c64dSAndroid Build Coastguard Worker
272*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.ACTIVE_TRACE_CHANGED, async (event) => {
273*90c8c64dSAndroid Build Coastguard Worker      this.timelineData.trySetActiveTrace(event.trace);
274*90c8c64dSAndroid Build Coastguard Worker      for (const viewer of this.viewers) {
275*90c8c64dSAndroid Build Coastguard Worker        await viewer.onWinscopeEvent(event);
276*90c8c64dSAndroid Build Coastguard Worker      }
277*90c8c64dSAndroid Build Coastguard Worker      await this.timelineComponent?.onWinscopeEvent(event);
278*90c8c64dSAndroid Build Coastguard Worker    });
279*90c8c64dSAndroid Build Coastguard Worker
280*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.DARK_MODE_TOGGLED, async (event) => {
281*90c8c64dSAndroid Build Coastguard Worker      await this.timelineComponent?.onWinscopeEvent(event);
282*90c8c64dSAndroid Build Coastguard Worker      for (const viewer of this.viewers) {
283*90c8c64dSAndroid Build Coastguard Worker        await viewer.onWinscopeEvent(event);
284*90c8c64dSAndroid Build Coastguard Worker      }
285*90c8c64dSAndroid Build Coastguard Worker    });
286*90c8c64dSAndroid Build Coastguard Worker
287*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
288*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.NO_TRACE_TARGETS_SELECTED,
289*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
290*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.add(new NoTraceTargetsSelected()).notify();
291*90c8c64dSAndroid Build Coastguard Worker      },
292*90c8c64dSAndroid Build Coastguard Worker    );
293*90c8c64dSAndroid Build Coastguard Worker
294*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
295*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.FILTER_PRESET_SAVE_REQUEST,
296*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
297*90c8c64dSAndroid Build Coastguard Worker        await this.findViewerByType(event.traceType)?.onWinscopeEvent(event);
298*90c8c64dSAndroid Build Coastguard Worker      },
299*90c8c64dSAndroid Build Coastguard Worker    );
300*90c8c64dSAndroid Build Coastguard Worker
301*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
302*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
303*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
304*90c8c64dSAndroid Build Coastguard Worker        await this.findViewerByType(event.traceType)?.onWinscopeEvent(event);
305*90c8c64dSAndroid Build Coastguard Worker      },
306*90c8c64dSAndroid Build Coastguard Worker    );
307*90c8c64dSAndroid Build Coastguard Worker
308*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.TRACE_SEARCH_REQUEST, async (event) => {
309*90c8c64dSAndroid Build Coastguard Worker      await this.timelineComponent?.onWinscopeEvent(event);
310*90c8c64dSAndroid Build Coastguard Worker      const searchViewer = this.viewers.find(
311*90c8c64dSAndroid Build Coastguard Worker        (viewer) => viewer.getViews()[0].type === ViewType.GLOBAL_SEARCH,
312*90c8c64dSAndroid Build Coastguard Worker      );
313*90c8c64dSAndroid Build Coastguard Worker      const trace = await this.tracePipeline.tryCreateSearchTrace(event.query);
314*90c8c64dSAndroid Build Coastguard Worker      this.timelineComponent?.onWinscopeEvent(new TraceSearchCompleted());
315*90c8c64dSAndroid Build Coastguard Worker      if (!trace) {
316*90c8c64dSAndroid Build Coastguard Worker        await searchViewer?.onWinscopeEvent(new TraceSearchFailed());
317*90c8c64dSAndroid Build Coastguard Worker        return;
318*90c8c64dSAndroid Build Coastguard Worker      }
319*90c8c64dSAndroid Build Coastguard Worker      const newSearchTrace = new TraceAddRequest(trace);
320*90c8c64dSAndroid Build Coastguard Worker      await searchViewer?.onWinscopeEvent(newSearchTrace);
321*90c8c64dSAndroid Build Coastguard Worker      if (trace.lengthEntries > 0 && !trace.isDumpWithoutTimestamp()) {
322*90c8c64dSAndroid Build Coastguard Worker        assertDefined(this.timelineData).getTraces().addTrace(trace);
323*90c8c64dSAndroid Build Coastguard Worker        await this.timelineComponent?.onWinscopeEvent(newSearchTrace);
324*90c8c64dSAndroid Build Coastguard Worker      }
325*90c8c64dSAndroid Build Coastguard Worker    });
326*90c8c64dSAndroid Build Coastguard Worker
327*90c8c64dSAndroid Build Coastguard Worker    await event.visit(WinscopeEventType.TRACE_REMOVE_REQUEST, async (event) => {
328*90c8c64dSAndroid Build Coastguard Worker      this.tracePipeline.getTraces().deleteTrace(event.trace);
329*90c8c64dSAndroid Build Coastguard Worker      if (this.timelineData.hasTrace(event.trace)) {
330*90c8c64dSAndroid Build Coastguard Worker        this.timelineData.getTraces().deleteTrace(event.trace);
331*90c8c64dSAndroid Build Coastguard Worker        await this.timelineComponent?.onWinscopeEvent(event);
332*90c8c64dSAndroid Build Coastguard Worker      }
333*90c8c64dSAndroid Build Coastguard Worker    });
334*90c8c64dSAndroid Build Coastguard Worker
335*90c8c64dSAndroid Build Coastguard Worker    await event.visit(
336*90c8c64dSAndroid Build Coastguard Worker      WinscopeEventType.INITIALIZE_TRACE_SEARCH_REQUEST,
337*90c8c64dSAndroid Build Coastguard Worker      async (event) => {
338*90c8c64dSAndroid Build Coastguard Worker        await this.timelineComponent?.onWinscopeEvent(event);
339*90c8c64dSAndroid Build Coastguard Worker        const traces = this.tracePipeline.getTraces();
340*90c8c64dSAndroid Build Coastguard Worker        const views = await TraceSearchInitializer.createSearchViews(traces);
341*90c8c64dSAndroid Build Coastguard Worker        const searchViewer = this.viewers.find(
342*90c8c64dSAndroid Build Coastguard Worker          (viewer) => viewer.getViews()[0].type === ViewType.GLOBAL_SEARCH,
343*90c8c64dSAndroid Build Coastguard Worker        );
344*90c8c64dSAndroid Build Coastguard Worker        const initializedEvent = new TraceSearchInitialized(views);
345*90c8c64dSAndroid Build Coastguard Worker        await searchViewer?.onWinscopeEvent(initializedEvent);
346*90c8c64dSAndroid Build Coastguard Worker        await this.timelineComponent?.onWinscopeEvent(initializedEvent);
347*90c8c64dSAndroid Build Coastguard Worker      },
348*90c8c64dSAndroid Build Coastguard Worker    );
349*90c8c64dSAndroid Build Coastguard Worker  }
350*90c8c64dSAndroid Build Coastguard Worker
351*90c8c64dSAndroid Build Coastguard Worker  private async loadFiles(files: File[], source: FilesSource) {
352*90c8c64dSAndroid Build Coastguard Worker    await this.tracePipeline.loadFiles(
353*90c8c64dSAndroid Build Coastguard Worker      files,
354*90c8c64dSAndroid Build Coastguard Worker      source,
355*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener,
356*90c8c64dSAndroid Build Coastguard Worker    );
357*90c8c64dSAndroid Build Coastguard Worker  }
358*90c8c64dSAndroid Build Coastguard Worker
359*90c8c64dSAndroid Build Coastguard Worker  private async propagateTracePosition(
360*90c8c64dSAndroid Build Coastguard Worker    position: TracePosition | undefined,
361*90c8c64dSAndroid Build Coastguard Worker    omitCrossToolProtocol: boolean,
362*90c8c64dSAndroid Build Coastguard Worker  ) {
363*90c8c64dSAndroid Build Coastguard Worker    if (!position) {
364*90c8c64dSAndroid Build Coastguard Worker      return;
365*90c8c64dSAndroid Build Coastguard Worker    }
366*90c8c64dSAndroid Build Coastguard Worker
367*90c8c64dSAndroid Build Coastguard Worker    const event = new TracePositionUpdate(position);
368*90c8c64dSAndroid Build Coastguard Worker    const viewers: Viewer[] = [...this.viewers].filter((viewer) =>
369*90c8c64dSAndroid Build Coastguard Worker      this.isViewerVisible(viewer),
370*90c8c64dSAndroid Build Coastguard Worker    );
371*90c8c64dSAndroid Build Coastguard Worker
372*90c8c64dSAndroid Build Coastguard Worker    const warnings: UserWarning[] = [];
373*90c8c64dSAndroid Build Coastguard Worker
374*90c8c64dSAndroid Build Coastguard Worker    for (const viewer of viewers) {
375*90c8c64dSAndroid Build Coastguard Worker      try {
376*90c8c64dSAndroid Build Coastguard Worker        await viewer.onWinscopeEvent(event);
377*90c8c64dSAndroid Build Coastguard Worker      } catch (e) {
378*90c8c64dSAndroid Build Coastguard Worker        const traceType = assertDefined(viewer.getTraces().at(0)?.type);
379*90c8c64dSAndroid Build Coastguard Worker        warnings.push(
380*90c8c64dSAndroid Build Coastguard Worker          new CannotVisualizeTraceEntry(
381*90c8c64dSAndroid Build Coastguard Worker            `Cannot parse entry for ${TRACE_INFO[traceType].name} trace: Trace may be corrupted.`,
382*90c8c64dSAndroid Build Coastguard Worker          ),
383*90c8c64dSAndroid Build Coastguard Worker        );
384*90c8c64dSAndroid Build Coastguard Worker      }
385*90c8c64dSAndroid Build Coastguard Worker    }
386*90c8c64dSAndroid Build Coastguard Worker
387*90c8c64dSAndroid Build Coastguard Worker    if (this.timelineComponent) {
388*90c8c64dSAndroid Build Coastguard Worker      await this.timelineComponent.onWinscopeEvent(event);
389*90c8c64dSAndroid Build Coastguard Worker    }
390*90c8c64dSAndroid Build Coastguard Worker
391*90c8c64dSAndroid Build Coastguard Worker    if (!omitCrossToolProtocol) {
392*90c8c64dSAndroid Build Coastguard Worker      await this.crossToolProtocol.onWinscopeEvent(event);
393*90c8c64dSAndroid Build Coastguard Worker    }
394*90c8c64dSAndroid Build Coastguard Worker
395*90c8c64dSAndroid Build Coastguard Worker    if (warnings.length > 0) {
396*90c8c64dSAndroid Build Coastguard Worker      warnings.forEach((w) => UserNotifier.add(w));
397*90c8c64dSAndroid Build Coastguard Worker    }
398*90c8c64dSAndroid Build Coastguard Worker  }
399*90c8c64dSAndroid Build Coastguard Worker
400*90c8c64dSAndroid Build Coastguard Worker  private isViewerVisible(viewer: Viewer): boolean {
401*90c8c64dSAndroid Build Coastguard Worker    if (!this.focusedTabView) {
402*90c8c64dSAndroid Build Coastguard Worker      // During initialization no tab is focused.
403*90c8c64dSAndroid Build Coastguard Worker      // Let's just consider all viewers as visible and to be updated.
404*90c8c64dSAndroid Build Coastguard Worker      return true;
405*90c8c64dSAndroid Build Coastguard Worker    }
406*90c8c64dSAndroid Build Coastguard Worker
407*90c8c64dSAndroid Build Coastguard Worker    return viewer.getViews().some((view) => {
408*90c8c64dSAndroid Build Coastguard Worker      if (view === this.focusedTabView) {
409*90c8c64dSAndroid Build Coastguard Worker        return true;
410*90c8c64dSAndroid Build Coastguard Worker      }
411*90c8c64dSAndroid Build Coastguard Worker      if (view.type === ViewType.OVERLAY) {
412*90c8c64dSAndroid Build Coastguard Worker        // Nice to have: update viewer only if overlay view is actually visible (not minimized)
413*90c8c64dSAndroid Build Coastguard Worker        return true;
414*90c8c64dSAndroid Build Coastguard Worker      }
415*90c8c64dSAndroid Build Coastguard Worker      return false;
416*90c8c64dSAndroid Build Coastguard Worker    });
417*90c8c64dSAndroid Build Coastguard Worker  }
418*90c8c64dSAndroid Build Coastguard Worker
419*90c8c64dSAndroid Build Coastguard Worker  private async processRemoteToolDeferredTimestampReceived(
420*90c8c64dSAndroid Build Coastguard Worker    deferredTimestamp: () => Timestamp | undefined,
421*90c8c64dSAndroid Build Coastguard Worker  ) {
422*90c8c64dSAndroid Build Coastguard Worker    this.lastRemoteToolDeferredTimestampReceived = deferredTimestamp;
423*90c8c64dSAndroid Build Coastguard Worker
424*90c8c64dSAndroid Build Coastguard Worker    if (!this.areViewersLoaded) {
425*90c8c64dSAndroid Build Coastguard Worker      return; // apply timestamp later when traces are visualized
426*90c8c64dSAndroid Build Coastguard Worker    }
427*90c8c64dSAndroid Build Coastguard Worker
428*90c8c64dSAndroid Build Coastguard Worker    const timestamp = deferredTimestamp();
429*90c8c64dSAndroid Build Coastguard Worker    if (!timestamp) {
430*90c8c64dSAndroid Build Coastguard Worker      return;
431*90c8c64dSAndroid Build Coastguard Worker    }
432*90c8c64dSAndroid Build Coastguard Worker
433*90c8c64dSAndroid Build Coastguard Worker    const position = this.timelineData.makePositionFromActiveTrace(timestamp);
434*90c8c64dSAndroid Build Coastguard Worker    this.timelineData.setPosition(position);
435*90c8c64dSAndroid Build Coastguard Worker
436*90c8c64dSAndroid Build Coastguard Worker    await this.propagateTracePosition(
437*90c8c64dSAndroid Build Coastguard Worker      this.timelineData.getCurrentPosition(),
438*90c8c64dSAndroid Build Coastguard Worker      true,
439*90c8c64dSAndroid Build Coastguard Worker    );
440*90c8c64dSAndroid Build Coastguard Worker    UserNotifier.notify();
441*90c8c64dSAndroid Build Coastguard Worker  }
442*90c8c64dSAndroid Build Coastguard Worker
443*90c8c64dSAndroid Build Coastguard Worker  private async processRemoteFilesReceived(files: File[], source: FilesSource) {
444*90c8c64dSAndroid Build Coastguard Worker    await this.resetAppToInitialState();
445*90c8c64dSAndroid Build Coastguard Worker    this.currentProgressListener = this.uploadTracesComponent;
446*90c8c64dSAndroid Build Coastguard Worker    await this.loadFiles(files, source);
447*90c8c64dSAndroid Build Coastguard Worker    UserNotifier.notify();
448*90c8c64dSAndroid Build Coastguard Worker  }
449*90c8c64dSAndroid Build Coastguard Worker
450*90c8c64dSAndroid Build Coastguard Worker  private async loadViewers() {
451*90c8c64dSAndroid Build Coastguard Worker    this.currentProgressListener?.onProgressUpdate(
452*90c8c64dSAndroid Build Coastguard Worker      'Computing frame mapping...',
453*90c8c64dSAndroid Build Coastguard Worker      undefined,
454*90c8c64dSAndroid Build Coastguard Worker    );
455*90c8c64dSAndroid Build Coastguard Worker
456*90c8c64dSAndroid Build Coastguard Worker    // TODO: move this into the ProgressListener
457*90c8c64dSAndroid Build Coastguard Worker    // allow the UI to update before making the main thread very busy
458*90c8c64dSAndroid Build Coastguard Worker    await TimeUtils.sleepMs(10);
459*90c8c64dSAndroid Build Coastguard Worker
460*90c8c64dSAndroid Build Coastguard Worker    this.tracePipeline.filterTracesWithoutVisualization();
461*90c8c64dSAndroid Build Coastguard Worker    if (this.tracePipeline.getTraces().getSize() === 0) {
462*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener?.onOperationFinished(false);
463*90c8c64dSAndroid Build Coastguard Worker      return;
464*90c8c64dSAndroid Build Coastguard Worker    }
465*90c8c64dSAndroid Build Coastguard Worker
466*90c8c64dSAndroid Build Coastguard Worker    try {
467*90c8c64dSAndroid Build Coastguard Worker      await this.tracePipeline.buildTraces();
468*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener?.onOperationFinished(true);
469*90c8c64dSAndroid Build Coastguard Worker    } catch (e) {
470*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(new IncompleteFrameMapping((e as Error).message));
471*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener?.onOperationFinished(false);
472*90c8c64dSAndroid Build Coastguard Worker    }
473*90c8c64dSAndroid Build Coastguard Worker
474*90c8c64dSAndroid Build Coastguard Worker    this.currentProgressListener?.onProgressUpdate(
475*90c8c64dSAndroid Build Coastguard Worker      'Initializing UI...',
476*90c8c64dSAndroid Build Coastguard Worker      undefined,
477*90c8c64dSAndroid Build Coastguard Worker    );
478*90c8c64dSAndroid Build Coastguard Worker
479*90c8c64dSAndroid Build Coastguard Worker    // TODO: move this into the ProgressListener
480*90c8c64dSAndroid Build Coastguard Worker    // allow the UI to update before making the main thread very busy
481*90c8c64dSAndroid Build Coastguard Worker    await TimeUtils.sleepMs(10);
482*90c8c64dSAndroid Build Coastguard Worker
483*90c8c64dSAndroid Build Coastguard Worker    try {
484*90c8c64dSAndroid Build Coastguard Worker      await this.timelineData.initialize(
485*90c8c64dSAndroid Build Coastguard Worker        this.tracePipeline.getTraces(),
486*90c8c64dSAndroid Build Coastguard Worker        await this.tracePipeline.getScreenRecordingVideo(),
487*90c8c64dSAndroid Build Coastguard Worker        this.tracePipeline.getTimestampConverter(),
488*90c8c64dSAndroid Build Coastguard Worker      );
489*90c8c64dSAndroid Build Coastguard Worker    } catch {
490*90c8c64dSAndroid Build Coastguard Worker      this.currentProgressListener?.onOperationFinished(false);
491*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(new FailedToInitializeTimelineData());
492*90c8c64dSAndroid Build Coastguard Worker      return;
493*90c8c64dSAndroid Build Coastguard Worker    }
494*90c8c64dSAndroid Build Coastguard Worker
495*90c8c64dSAndroid Build Coastguard Worker    this.viewers = new ViewerFactory().createViewers(
496*90c8c64dSAndroid Build Coastguard Worker      this.tracePipeline.getTraces(),
497*90c8c64dSAndroid Build Coastguard Worker      this.storage,
498*90c8c64dSAndroid Build Coastguard Worker    );
499*90c8c64dSAndroid Build Coastguard Worker    this.viewers.forEach((viewer) =>
500*90c8c64dSAndroid Build Coastguard Worker      viewer.setEmitEvent(async (event) => {
501*90c8c64dSAndroid Build Coastguard Worker        await this.onWinscopeEvent(event);
502*90c8c64dSAndroid Build Coastguard Worker      }),
503*90c8c64dSAndroid Build Coastguard Worker    );
504*90c8c64dSAndroid Build Coastguard Worker
505*90c8c64dSAndroid Build Coastguard Worker    // Set initial trace position as soon as UI is created
506*90c8c64dSAndroid Build Coastguard Worker    const initialPosition = this.getInitialTracePosition();
507*90c8c64dSAndroid Build Coastguard Worker    this.timelineData.setPosition(initialPosition);
508*90c8c64dSAndroid Build Coastguard Worker
509*90c8c64dSAndroid Build Coastguard Worker    // Make sure all viewers are initialized and have performed the heavy pre-processing they need
510*90c8c64dSAndroid Build Coastguard Worker    // at this stage, while the "initializing UI" progress message is still being displayed.
511*90c8c64dSAndroid Build Coastguard Worker    // The viewers initialization is triggered by sending them a "trace position update".
512*90c8c64dSAndroid Build Coastguard Worker    await this.propagateTracePosition(initialPosition, true);
513*90c8c64dSAndroid Build Coastguard Worker
514*90c8c64dSAndroid Build Coastguard Worker    this.focusedTabView = this.viewers
515*90c8c64dSAndroid Build Coastguard Worker      .find((v) => v.getViews()[0].type === ViewType.TRACE_TAB)
516*90c8c64dSAndroid Build Coastguard Worker      ?.getViews()[0];
517*90c8c64dSAndroid Build Coastguard Worker    this.areViewersLoaded = true;
518*90c8c64dSAndroid Build Coastguard Worker
519*90c8c64dSAndroid Build Coastguard Worker    // Notify app component (i.e. render viewers), only after all viewers have been initialized
520*90c8c64dSAndroid Build Coastguard Worker    // (see above).
521*90c8c64dSAndroid Build Coastguard Worker    //
522*90c8c64dSAndroid Build Coastguard Worker    // Notifying the app component first could result in this kind of interleaved execution:
523*90c8c64dSAndroid Build Coastguard Worker    // 1. Mediator notifies app component
524*90c8c64dSAndroid Build Coastguard Worker    //    1.1. App component renders UI components
525*90c8c64dSAndroid Build Coastguard Worker    //    1.2. Mediator receives back a "view switched" event
526*90c8c64dSAndroid Build Coastguard Worker    //    1.2. Mediator sends "trace position update" to viewers
527*90c8c64dSAndroid Build Coastguard Worker    // 2. Mediator sends "trace position update" to viewers to initialize them (see above)
528*90c8c64dSAndroid Build Coastguard Worker    //
529*90c8c64dSAndroid Build Coastguard Worker    // and because our data load operations are async and involve task suspensions, the two
530*90c8c64dSAndroid Build Coastguard Worker    // "trace position update" could be processed concurrently within the same viewer.
531*90c8c64dSAndroid Build Coastguard Worker    // Meaning the viewer could perform twice the initial heavy pre-processing,
532*90c8c64dSAndroid Build Coastguard Worker    // thus increasing UI initialization times.
533*90c8c64dSAndroid Build Coastguard Worker    await this.appComponent.onWinscopeEvent(new ViewersLoaded(this.viewers));
534*90c8c64dSAndroid Build Coastguard Worker  }
535*90c8c64dSAndroid Build Coastguard Worker
536*90c8c64dSAndroid Build Coastguard Worker  private getInitialTracePosition(): TracePosition | undefined {
537*90c8c64dSAndroid Build Coastguard Worker    if (this.lastRemoteToolDeferredTimestampReceived) {
538*90c8c64dSAndroid Build Coastguard Worker      const lastRemoteToolTimestamp =
539*90c8c64dSAndroid Build Coastguard Worker        this.lastRemoteToolDeferredTimestampReceived();
540*90c8c64dSAndroid Build Coastguard Worker      if (lastRemoteToolTimestamp) {
541*90c8c64dSAndroid Build Coastguard Worker        return this.timelineData.makePositionFromActiveTrace(
542*90c8c64dSAndroid Build Coastguard Worker          lastRemoteToolTimestamp,
543*90c8c64dSAndroid Build Coastguard Worker        );
544*90c8c64dSAndroid Build Coastguard Worker      }
545*90c8c64dSAndroid Build Coastguard Worker    }
546*90c8c64dSAndroid Build Coastguard Worker
547*90c8c64dSAndroid Build Coastguard Worker    const position = this.timelineData.getCurrentPosition();
548*90c8c64dSAndroid Build Coastguard Worker    if (position) {
549*90c8c64dSAndroid Build Coastguard Worker      return position;
550*90c8c64dSAndroid Build Coastguard Worker    }
551*90c8c64dSAndroid Build Coastguard Worker
552*90c8c64dSAndroid Build Coastguard Worker    // TimelineData might not provide a TracePosition because all the loaded traces are
553*90c8c64dSAndroid Build Coastguard Worker    // dumps with invalid timestamps (value zero). In this case let's create a TracePosition
554*90c8c64dSAndroid Build Coastguard Worker    // out of any entry from the loaded traces (if available).
555*90c8c64dSAndroid Build Coastguard Worker    const firstEntries = this.tracePipeline
556*90c8c64dSAndroid Build Coastguard Worker      .getTraces()
557*90c8c64dSAndroid Build Coastguard Worker      .mapTrace((trace) => {
558*90c8c64dSAndroid Build Coastguard Worker        if (trace.lengthEntries > 0) {
559*90c8c64dSAndroid Build Coastguard Worker          return trace.getEntry(0);
560*90c8c64dSAndroid Build Coastguard Worker        }
561*90c8c64dSAndroid Build Coastguard Worker        return undefined;
562*90c8c64dSAndroid Build Coastguard Worker      })
563*90c8c64dSAndroid Build Coastguard Worker      .filter((entry) => {
564*90c8c64dSAndroid Build Coastguard Worker        return entry !== undefined;
565*90c8c64dSAndroid Build Coastguard Worker      }) as Array<TraceEntry<object>>;
566*90c8c64dSAndroid Build Coastguard Worker
567*90c8c64dSAndroid Build Coastguard Worker    if (firstEntries.length > 0) {
568*90c8c64dSAndroid Build Coastguard Worker      return TracePosition.fromTraceEntry(firstEntries[0]);
569*90c8c64dSAndroid Build Coastguard Worker    }
570*90c8c64dSAndroid Build Coastguard Worker
571*90c8c64dSAndroid Build Coastguard Worker    return undefined;
572*90c8c64dSAndroid Build Coastguard Worker  }
573*90c8c64dSAndroid Build Coastguard Worker
574*90c8c64dSAndroid Build Coastguard Worker  private async resetAppToInitialState() {
575*90c8c64dSAndroid Build Coastguard Worker    this.tracePipeline.clear();
576*90c8c64dSAndroid Build Coastguard Worker    this.timelineData.clear();
577*90c8c64dSAndroid Build Coastguard Worker    this.viewers = [];
578*90c8c64dSAndroid Build Coastguard Worker    this.areViewersLoaded = false;
579*90c8c64dSAndroid Build Coastguard Worker    this.lastRemoteToolDeferredTimestampReceived = undefined;
580*90c8c64dSAndroid Build Coastguard Worker    this.focusedTabView = undefined;
581*90c8c64dSAndroid Build Coastguard Worker    await this.appComponent.onWinscopeEvent(new ViewersUnloaded());
582*90c8c64dSAndroid Build Coastguard Worker  }
583*90c8c64dSAndroid Build Coastguard Worker
584*90c8c64dSAndroid Build Coastguard Worker  private async propagateToOverlays(event: ExpandedTimelineToggled) {
585*90c8c64dSAndroid Build Coastguard Worker    const overlayViewers = this.viewers.filter((viewer) =>
586*90c8c64dSAndroid Build Coastguard Worker      viewer.getViews().some((view) => view.type === ViewType.OVERLAY),
587*90c8c64dSAndroid Build Coastguard Worker    );
588*90c8c64dSAndroid Build Coastguard Worker    for (const overlay of overlayViewers) {
589*90c8c64dSAndroid Build Coastguard Worker      await overlay.onWinscopeEvent(event);
590*90c8c64dSAndroid Build Coastguard Worker    }
591*90c8c64dSAndroid Build Coastguard Worker  }
592*90c8c64dSAndroid Build Coastguard Worker
593*90c8c64dSAndroid Build Coastguard Worker  private findViewerByType(type: TraceType): Viewer | undefined {
594*90c8c64dSAndroid Build Coastguard Worker    return this.viewers.find((viewer) => viewer.getTraces()[0].type === type);
595*90c8c64dSAndroid Build Coastguard Worker  }
596*90c8c64dSAndroid Build Coastguard Worker}
597