• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {HostOsByteStream} from '../host_os_byte_stream';
16import {RecordingError} from '../recording_error_handling';
17import {
18  DataSource,
19  HostOsTargetInfo,
20  OnDisconnectCallback,
21  OnTargetChangeCallback,
22  RecordingTargetV2,
23  TracingSession,
24  TracingSessionListener,
25} from '../recording_interfaces_v2';
26import {
27  isLinux,
28  isMacOs,
29  WEBSOCKET_CLOSED_ABNORMALLY_CODE,
30} from '../recording_utils';
31import {TracedTracingSession} from '../traced_tracing_session';
32
33export class HostOsTarget implements RecordingTargetV2 {
34  private readonly targetType: 'LINUX'|'MACOS';
35  private readonly name: string;
36  private websocket: WebSocket;
37  private streams = new Set<HostOsByteStream>();
38  private dataSources?: DataSource[];
39  private onDisconnect: OnDisconnectCallback = (_) => {};
40
41  constructor(
42      websocketUrl: string,
43      private maybeClearTarget: (target: HostOsTarget) => void,
44      private onTargetChange: OnTargetChangeCallback) {
45    if (isMacOs(navigator.userAgent)) {
46      this.name = 'MacOS';
47      this.targetType = 'MACOS';
48    } else if (isLinux(navigator.userAgent)) {
49      this.name = 'Linux';
50      this.targetType = 'LINUX';
51    } else {
52      throw new RecordingError(
53          'Host OS target created on an unsupported operating system.');
54    }
55
56    this.websocket = new WebSocket(websocketUrl);
57    this.websocket.onclose = this.onClose.bind(this);
58    // 'onError' gets called when the websocketURL where the UI tries to connect
59    // is disallowed by the Content Security Policy. In this case, we disconnect
60    // the target.
61    this.websocket.onerror = this.disconnect.bind(this);
62  }
63
64  getInfo(): HostOsTargetInfo {
65    return {
66      targetType: this.targetType,
67      name: this.name,
68      dataSources: this.dataSources || [],
69    };
70  }
71
72  canCreateTracingSession(): boolean {
73    return true;
74  }
75
76  async createTracingSession(tracingSessionListener: TracingSessionListener):
77      Promise<TracingSession> {
78    this.onDisconnect = tracingSessionListener.onDisconnect;
79
80    const osStream = await HostOsByteStream.create(this.getUrl());
81    this.streams.add(osStream);
82    const tracingSession =
83        new TracedTracingSession(osStream, tracingSessionListener);
84    await tracingSession.initConnection();
85
86    if (!this.dataSources) {
87      this.dataSources = await tracingSession.queryServiceState();
88      this.onTargetChange();
89    }
90    return tracingSession;
91  }
92
93  // Starts a tracing session in order to fetch data sources from the
94  // device. Then, it cancels the session.
95  async fetchTargetInfo(tracingSessionListener: TracingSessionListener):
96      Promise<void> {
97    const tracingSession =
98        await this.createTracingSession(tracingSessionListener);
99    tracingSession.cancel();
100  }
101
102  async disconnect(): Promise<void> {
103    if (this.websocket.readyState === this.websocket.OPEN) {
104      this.websocket.close();
105      // We remove the 'onclose' callback so the 'disconnect' method doesn't get
106      // executed twice.
107      this.websocket.onclose = null;
108    }
109    for (const stream of this.streams) {
110      stream.close();
111    }
112    // We remove the existing target from the factory if present.
113    this.maybeClearTarget(this);
114    // We run the onDisconnect callback in case this target is used for tracing.
115    this.onDisconnect();
116  }
117
118  // We can connect to the Host OS without taking the connection away from
119  // another process.
120  async canConnectWithoutContention(): Promise<boolean> {
121    return true;
122  }
123
124  getUrl() {
125    return this.websocket.url;
126  }
127
128  private onClose(ev: CloseEvent): void {
129    if (ev.code === WEBSOCKET_CLOSED_ABNORMALLY_CODE) {
130      console.info(
131          `It's safe to ignore the 'WebSocket connection to ${
132              this.getUrl()} error above, if present. It occurs when ` +
133          'checking the connection to the local Websocket server.');
134    }
135    this.disconnect();
136  }
137}
138