// Copyright (C) 2019 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 rpc from 'noice-json-rpc'; // To really understand how this works it is useful to see the implementation // of noice-json-rpc. export class DevToolsSocket implements rpc.LikeSocket { private messageCallback: Function = (_: string) => {}; private openCallback: Function = () => {}; private closeCallback: Function = () => {}; private target: chrome.debugger.Debuggee|undefined; constructor() { chrome.debugger.onDetach.addListener(this.onDetach.bind(this)); chrome.debugger.onEvent.addListener((_source, method, params) => { if (this.messageCallback) { const msg: rpc.JsonRpc2.Notification = {method, params}; this.messageCallback(JSON.stringify(msg)); } }); } send(message: string): void { if (this.target === undefined) return; const msg: rpc.JsonRpc2.Request = JSON.parse(message); chrome.debugger.sendCommand( this.target, msg.method, msg.params, (result) => { if (result === undefined) result = {}; const response: rpc.JsonRpc2.Response = {id: msg.id, result}; this.messageCallback(JSON.stringify(response)); }); } // This method will be called once for each event soon after the creation of // this object. To understand better what happens, checking the implementation // of noice-json-rpc is very useful. // While the events "message" and "open" are for implementing the LikeSocket, // "close" is a callback set from ChromeTracingController, to reset the state // after a detach. on(event: string, cb: Function) { if (event === 'message') { this.messageCallback = cb; } else if (event === 'open') { this.openCallback = cb; } else if (event === 'close') { this.closeCallback = cb; } } removeListener(_event: string, _cb: Function) { throw new Error('Call unexpected'); } attachToBrowser(then: (error?: string) => void) { this.attachToTarget({targetId: 'browser'}, then); } private attachToTarget( target: chrome.debugger.Debuggee, then: (error?: string) => void) { chrome.debugger.attach(target, /* requiredVersion=*/ '1.3', () => { if (chrome.runtime.lastError) { then(chrome.runtime.lastError.message); return; } this.target = target; this.openCallback(); then(); }); } detach() { if (this.target === undefined) return; chrome.debugger.detach(this.target, () => { this.target = undefined; }); } onDetach(_source: chrome.debugger.Debuggee, _reason: string) { if (_source === this.target) { this.target = undefined; this.closeCallback(); } } isAttached(): boolean { return this.target !== undefined; } }