|  | // 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 * as 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; | 
|  | } | 
|  | } |