xref: /aosp_15_r20/external/perfetto/ui/src/chrome_extension/devtools_socket.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2019 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 {JsonRpc2, LikeSocket} from 'noice-json-rpc';
16
17// To really understand how this works it is useful to see the implementation
18// of noice-json-rpc.
19export class DevToolsSocket implements LikeSocket {
20  private messageCallback: Function = (_: string) => {};
21  private openCallback: Function = () => {};
22  private closeCallback: Function = () => {};
23  private target: chrome.debugger.Debuggee | undefined;
24
25  constructor() {
26    chrome.debugger.onDetach.addListener(this.onDetach.bind(this));
27    chrome.debugger.onEvent.addListener((_source, method, params) => {
28      // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
29      if (this.messageCallback) {
30        const msg: JsonRpc2.Notification = {method, params};
31        this.messageCallback(JSON.stringify(msg));
32      }
33    });
34  }
35
36  send(message: string): void {
37    if (this.target === undefined) return;
38
39    const msg: JsonRpc2.Request = JSON.parse(message);
40    chrome.debugger.sendCommand(
41      this.target,
42      msg.method,
43      msg.params,
44      (result) => {
45        if (result === undefined) result = {};
46        const response: JsonRpc2.Response = {id: msg.id, result};
47        this.messageCallback(JSON.stringify(response));
48      },
49    );
50  }
51
52  // This method will be called once for each event soon after the creation of
53  // this object. To understand better what happens, checking the implementation
54  // of noice-json-rpc is very useful.
55  // While the events "message" and "open" are for implementing the LikeSocket,
56  // "close" is a callback set from ChromeTracingController, to reset the state
57  // after a detach.
58  on(event: string, cb: Function) {
59    if (event === 'message') {
60      this.messageCallback = cb;
61    } else if (event === 'open') {
62      this.openCallback = cb;
63    } else if (event === 'close') {
64      this.closeCallback = cb;
65    }
66  }
67
68  removeListener(_event: string, _cb: Function) {
69    throw new Error('Call unexpected');
70  }
71
72  attachToBrowser(then: (error?: string) => void) {
73    this.attachToTarget({targetId: 'browser'}, then);
74  }
75
76  private attachToTarget(
77    target: chrome.debugger.Debuggee,
78    then: (error?: string) => void,
79  ) {
80    chrome.debugger.attach(target, /* requiredVersion=*/ '1.3', () => {
81      if (chrome.runtime.lastError) {
82        then(chrome.runtime.lastError.message);
83        return;
84      }
85      this.target = target;
86      this.openCallback();
87      then();
88    });
89  }
90
91  detach() {
92    if (this.target === undefined) return;
93
94    chrome.debugger.detach(this.target, () => {
95      this.target = undefined;
96    });
97  }
98
99  onDetach(_source: chrome.debugger.Debuggee, _reason: string) {
100    if (_source === this.target) {
101      this.target = undefined;
102      this.closeCallback();
103    }
104  }
105
106  isAttached(): boolean {
107    return this.target !== undefined;
108  }
109}
110