diff --git a/package.json b/package.json index 4db943819..099be0365 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "glob": "^7.0.6", "request-light": "^0.1.0", + "noice-json-rpc": "^1.0.0", "source-map": "^0.5.6", "vscode-debugadapter": "^1.13.0", "vscode-debugprotocol": "^1.13.0", @@ -28,6 +29,7 @@ "@types/source-map": "^0.1.27", "@types/ws": "0.0.32", "del": "^2.2.2", + "chrome-remote-debug-protocol": "^1.1.20160730", "gulp": "^3.9.1", "gulp-debug": "^2.1.2", "gulp-mocha": "^3.0.1", diff --git a/src/chrome/chromeConnection.ts b/src/chrome/chromeConnection.ts index 24678b69d..3bb01a795 100644 --- a/src/chrome/chromeConnection.ts +++ b/src/chrome/chromeConnection.ts @@ -3,141 +3,61 @@ *--------------------------------------------------------*/ import * as WebSocket from 'ws'; -import {EventEmitter} from 'events'; import * as errors from '../errors'; import * as utils from '../utils'; import * as logger from '../logger'; -import * as Chrome from './chromeDebugProtocol'; import {getChromeTargetWebSocketURL} from './chromeTargetDiscoveryStrategy'; -interface IMessageWithId { - id: number; - method: string; - params?: string[]; +import {Client} from 'noice-json-rpc'; +import Crdp from 'chrome-remote-debug-protocol'; + +export interface ITarget { + description: string; + devtoolsFrontendUrl: string; + id: string; + thumbnailUrl?: string; + title: string; + type: string; + url?: string; + webSocketDebuggerUrl: string; } /** - * Implements a Request/Response API on top of a WebSocket for messages that are marked with an `id` property. - * Emits `message.method` for messages that don't have `id`. + * A subclass of WebSocket that logs all traffic */ -class ResReqWebSocket extends EventEmitter { - private _pendingRequests = new Map(); - private _wsAttached: Promise; +class LoggingSocket extends WebSocket { + constructor(address: string, protocols?: string | string[], options?: WebSocket.IClientOptions) { + super(address, protocols, options); - public get isOpen(): boolean { return !!this._wsAttached; } - - /** - * Attach to the given websocket url - */ - public open(wsUrl: string): Promise { - this._wsAttached = new Promise((resolve, reject) => { - let ws: WebSocket; - try { - ws = new WebSocket(wsUrl); - } catch (e) { - // invalid url e.g. - reject(e.message); - return; - } - - // WebSocket will try to connect for 20+ seconds before timing out. - // Implement a shorter timeout here - setTimeout(() => reject('WebSocket connection timed out'), 10000); - - // if 'error' is fired while connecting, reject the promise - ws.on('error', reject); - ws.on('open', () => { - // Replace the promise-rejecting handler - ws.removeListener('error', reject); - - ws.on('error', e => { - logger.log('Websocket error: ' + e.toString()); - this.emit('error', e); - }); - - resolve(ws); - }); - ws.on('message', msgStr => { - const msgObj = JSON.parse(msgStr); - if (msgObj - && !(msgObj.method === 'Debugger.scriptParsed' && msgObj.params && msgObj.params.isContentScript) - && !(msgObj.params && msgObj.params.url && msgObj.params.url.indexOf('extensions::') === 0)) { - // Not really the right place to examine the content of the message, but don't log annoying extension script notifications. - logger.verbose('From target: ' + msgStr); - } - - this.onMessage(msgObj); - }); - ws.on('close', () => { - logger.log('Websocket closed'); - this.emit('close'); - }); + this.on('error', e => { + logger.log('Websocket error: ' + e.toString()); }); - return this._wsAttached.then(() => { }); - } - - public close(): void { - if (this._wsAttached) { - this._wsAttached.then(ws => ws.close()); - this._wsAttached = null; - } - } - - /** - * Send a message which must have an id. Ok to call immediately after attach. Messages will be queued until - * the websocket actually attaches. - */ - public sendMessage(message: IMessageWithId): Promise { - return new Promise((resolve, reject) => { - this._pendingRequests.set(message.id, resolve); - this._wsAttached.then(ws => { - const msgStr = JSON.stringify(message); - logger.verbose('To target: ' + msgStr); - ws.send(msgStr); - }); + this.on('close', () => { + logger.log('Websocket closed'); }); - } - /** - * Wrap EventEmitter.emit in try/catch and log, for errors thrown in subscribers - */ - public emit(event: string, ...args: any[]): boolean { - try { - return super.emit.apply(this, arguments); - } catch (e) { - logger.error('Error while handling target event: ' + e.stack); - return false; - } + this.on('message', msgStr => { + const msgObj = JSON.parse(msgStr); + if (msgObj + && !(msgObj.method === 'Debugger.scriptParsed' && msgObj.params && msgObj.params.isContentScript) + && !(msgObj.params && msgObj.params.url && msgObj.params.url.indexOf('extensions::') === 0)) { + // Not really the right place to examine the content of the message, but don't log annoying extension script notifications. + logger.verbose('From target: ' + msgStr); + } + }); } - private onMessage(message: any): void { - if (typeof message.id === 'number') { - if (this._pendingRequests.has(message.id)) { - // Resolve the pending request with this response - this._pendingRequests.get(message.id)(message); - this._pendingRequests.delete(message.id); - } else { - logger.error(`Got a response with id ${message.id} for which there is no pending request.`); - } - } else if (message.method) { - this.emit(message.method, message.params); - } else { - // Message is malformed - safely stringify and log it - let messageStr: string; - try { - messageStr = JSON.stringify(message); - } catch (e) { - messageStr = '' + message; - } + public send(data: any, cb?: (err: Error) => void): void { + super.send.apply(this, arguments); - logger.error(`Got a response with no id nor method property: ${messageStr}`); - } + const msgStr = JSON.stringify(data); + logger.verbose('To target: ' + msgStr); } } -export type ITargetFilter = (target: Chrome.ITarget) => boolean; +export type ITargetFilter = (target: ITarget) => boolean; export type ITargetDiscoveryStrategy = (address: string, port: number, targetFilter?: ITargetFilter, targetUrl?: string) => Promise; /** @@ -146,23 +66,24 @@ export type ITargetDiscoveryStrategy = (address: string, port: number, targetFil export class ChromeConnection { private static ATTACH_TIMEOUT = 10000; // ms - private _nextId: number; - private _socket: ResReqWebSocket; + private _socket: WebSocket; + private _client: Client; private _targetFilter: ITargetFilter; private _targetDiscoveryStrategy: ITargetDiscoveryStrategy; constructor(targetDiscovery?: ITargetDiscoveryStrategy, targetFilter?: ITargetFilter) { this._targetFilter = targetFilter; this._targetDiscoveryStrategy = targetDiscovery || getChromeTargetWebSocketURL; - - // this._socket should exist before attaching so consumers can call on() before attach, which fires events - this.reset(); } - public get isAttached(): boolean { return this._socket.isOpen; } + public get isAttached(): boolean { return !!this._client; } public on(eventName: string, handler: (msg: any) => void): void { - this._socket.on(eventName, handler); + this._client.on(eventName, handler); + } + + public get api(): Crdp.CrdpClient { + return this._client.api(); } /** @@ -170,143 +91,29 @@ export class ChromeConnection { */ public attach(address = '127.0.0.1', port = 9222, targetUrl?: string): Promise { return this._attach(address, port, targetUrl) - .then(() => Promise.all([ - this.sendMessage('Runtime.enable'), - this.sendMessage('Debugger.enable'), - this.run()])) .then(() => { }); } private _attach(address: string, port: number, targetUrl?: string, timeout = ChromeConnection.ATTACH_TIMEOUT): Promise { return utils.retryAsync(() => this._targetDiscoveryStrategy(address, port, this._targetFilter, targetUrl), timeout, /*intervalDelay=*/200) .catch(err => Promise.reject(errors.runtimeConnectionTimeout(timeout, err.message))) - .then(wsUrl => this._socket.open(wsUrl)); + .then(wsUrl => { + this._socket = new LoggingSocket(wsUrl); + this._client = new Client(this._socket); + }); } - private run(): Promise { + public run(): Promise { // This is a CDP version difference which will have to be handled more elegantly with others later... // For now, we need to send both messages and ignore a failing one. return Promise.all([ - this.runtime_runIfWaitingForDebugger(), - this.runtime_run() + this.api.Runtime.runIfWaitingForDebugger(), + (this.api.Runtime).run() ]) .then(() => { }, e => { }); } public close(): void { this._socket.close(); - this.reset(); - } - - private reset(): void { - this._nextId = 1; - this._socket = new ResReqWebSocket(); - } - - public debugger_setBreakpoint(location: Chrome.Debugger.Location, condition?: string): Promise { - return this.sendMessage('Debugger.setBreakpoint', { location, condition }); - } - - public debugger_setBreakpointByUrlRegex(urlRegex: string, lineNumber: number, columnNumber: number, condition?: string): Promise { - return this.sendMessage('Debugger.setBreakpointByUrl', { urlRegex, lineNumber, columnNumber, condition }); - } - - public debugger_removeBreakpoint(breakpointId: string): Promise { - return this.sendMessage('Debugger.removeBreakpoint', { breakpointId }); - } - - public debugger_stepOver(): Promise { - return this.sendMessage('Debugger.stepOver'); - } - - public debugger_stepIn(): Promise { - return this.sendMessage('Debugger.stepInto'); - } - - public debugger_stepOut(): Promise { - return this.sendMessage('Debugger.stepOut'); - } - - public debugger_resume(): Promise { - return this.sendMessage('Debugger.resume'); - } - - public debugger_pause(): Promise { - return this.sendMessage('Debugger.pause'); - } - - public debugger_evaluateOnCallFrame(callFrameId: string, expression: string, objectGroup = 'dummyObjectGroup', returnByValue?: boolean, silent?: boolean): Promise { - return this.sendMessage('Debugger.evaluateOnCallFrame', { callFrameId, expression, objectGroup, returnByValue, silent }); - } - - public debugger_setPauseOnExceptions(state: string): Promise { - return this.sendMessage('Debugger.setPauseOnExceptions', { state }); - } - - public debugger_getScriptSource(scriptId: Chrome.Debugger.ScriptId): Promise { - return this.sendMessage('Debugger.getScriptSource', { scriptId }); - } - - public debugger_setVariableValue(callFrameId: string, scopeNumber: number, variableName: string, newValue: Chrome.Runtime.CallArgument): Promise { - return this.sendMessage('Debugger.setVariableValue', { callFrameId, scopeNumber, variableName, newValue }); - } - - public runtime_getProperties(objectId: string, ownProperties: boolean, accessorPropertiesOnly: boolean, generatePreview?: boolean): Promise { - return this.sendMessage('Runtime.getProperties', { objectId, ownProperties, accessorPropertiesOnly, generatePreview }); - } - - public runtime_evaluate(expression: string, objectGroup?: string, contextId = 1, returnByValue = false, silent?: boolean): Promise { - return this.sendMessage('Runtime.evaluate', { expression, objectGroup, contextId, returnByValue, silent }); - } - - public runtime_callFunctionOn(objectId: string, functionDeclaration: string, args?: Chrome.Runtime.CallArgument[], silent?: boolean, returnByValue?: boolean, - generatePreview?: boolean, userGesture?: boolean, awaitPromise?: boolean): Promise { - return this.sendMessage('Runtime.callFunctionOn', { objectId, functionDeclaration, arguments: args, silent, returnByValue, generatePreview, userGesture, awaitPromise }); - } - - public runtime_runIfWaitingForDebugger(): Promise { - return this.sendMessage('Runtime.runIfWaitingForDebugger'); - } - - public runtime_run(): Promise { - return this.sendMessage('Runtime.run'); - } - - public page_configureOverlay(message: string): Promise { - return this.sendMessage('Page.setOverlayMessage', { message }); - } - - public emulation_clearDeviceMetricsOverride(): Promise { - return this.sendMessage('Emulation.clearDeviceMetricsOverride'); - } - - public emulation_setEmulatedMedia(media: string): Promise { - return this.sendMessage('Emulation.setEmulatedMedia', { media }); - } - - public emulation_setTouchEmulationEnabled(enabled: boolean, configuration?: string): Promise { - let messageData: { enabled: boolean; configuration?: string; } = { enabled }; - - if (configuration) { - messageData.configuration = configuration; - } - - return this.sendMessage('Emulation.setTouchEmulationEnabled', messageData); - } - - public emulation_resetScrollAndPageScaleFactor(): Promise { - return this.sendMessage('Emulation.resetScrollAndPageScaleFactor'); - } - - public emulation_setDeviceMetricsOverride(metrics: Chrome.Emulation.SetDeviceMetricsOverrideParams): Promise { - return this.sendMessage('Emulation.setDeviceMetricsOverride', metrics); - } - - private sendMessage(method: any, params?: any): Promise { - return this._socket.sendMessage({ - id: this._nextId++, - method, - params - }); } } diff --git a/src/chrome/chromeDebugAdapter.ts b/src/chrome/chromeDebugAdapter.ts index 3263de752..3d6f39ad9 100644 --- a/src/chrome/chromeDebugAdapter.ts +++ b/src/chrome/chromeDebugAdapter.ts @@ -11,7 +11,7 @@ import {ILaunchRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, IS import {IChromeDebugAdapterOpts, ChromeDebugSession} from './chromeDebugSession'; import {ChromeConnection} from './chromeConnection'; import * as ChromeUtils from './chromeUtils'; -import * as Chrome from './chromeDebugProtocol'; +import Crdp from 'chrome-remote-debug-protocol'; import {PropertyContainer, ScopeContainer, IVariableContainer, ExceptionContainer, isIndexedPropName} from './variables'; import {formatConsoleMessage} from './consoleHelper'; @@ -38,7 +38,7 @@ interface IPropCount { */ export interface ISourceContainer { /** The runtime-side scriptId of this script */ - scriptId?: Chrome.Debugger.ScriptId; + scriptId?: Crdp.Runtime.ScriptId; /** The contents of this script, if they are inlined in the sourcemap */ contents?: string; /** The authored path to this script (only set if the contents are inlined) */ @@ -58,10 +58,10 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { protected _session: ChromeDebugSession; private _clientAttached: boolean; - private _currentStack: Chrome.Debugger.CallFrame[]; - private _committedBreakpointsByUrl: Map; + private _currentStack: Crdp.Debugger.CallFrame[]; + private _committedBreakpointsByUrl: Map; private _overlayHelper: utils.DebounceHelper; - private _exception: Chrome.Runtime.RemoteObject; + private _exception: Crdp.Runtime.RemoteObject; private _setBreakpointsRequestQ: Promise; private _expectingResumedEvent: boolean; protected _expectingStopReason: string; @@ -70,11 +70,11 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { private _breakpointIdHandles: utils.ReverseHandles; private _sourceHandles: Handles; - private _scriptsById: Map; - private _scriptsByUrl: Map; + private _scriptsById: Map; + private _scriptsByUrl: Map; private _pendingBreakpointsByUrl: Map; - protected _chromeConnection: ChromeConnection; + private _chromeConnection: ChromeConnection; private _lineColTransformer: LineColTransformer; protected _sourceMapTransformer: BaseSourceMapTransformer; @@ -109,16 +109,20 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return !!this._currentStack; } + protected get chrome(): Crdp.CrdpClient { + return this._chromeConnection.api; + } + /** * Called on 'clearEverything' or on a navigation/refresh */ protected clearTargetContext(): void { this._sourceMapTransformer.clearTargetContext(); - this._scriptsById = new Map(); - this._scriptsByUrl = new Map(); + this._scriptsById = new Map(); + this._scriptsByUrl = new Map(); - this._committedBreakpointsByUrl = new Map(); + this._committedBreakpointsByUrl = new Map(); this._setBreakpointsRequestQ = Promise.resolve(); this._pathTransformer.clearTargetContext(); @@ -224,19 +228,27 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { // Client is attaching - if not attached to the chrome target, create a connection and attach this._clientAttached = true; if (!this._chromeConnection.isAttached) { - this._chromeConnection.on('Debugger.paused', params => this.onDebuggerPaused(params)); - this._chromeConnection.on('Debugger.resumed', () => this.onDebuggerResumed()); - this._chromeConnection.on('Debugger.scriptParsed', params => this.onScriptParsed(params)); - this._chromeConnection.on('Debugger.globalObjectCleared', () => this.onGlobalObjectCleared()); - this._chromeConnection.on('Debugger.breakpointResolved', params => this.onBreakpointResolved(params)); - - this._chromeConnection.on('Runtime.consoleAPICalled', params => this.onConsoleMessage(params)); - - this._chromeConnection.on('Inspector.detached', () => this.terminateSession('Debug connection detached')); - this._chromeConnection.on('close', () => this.terminateSession('Debug connection closed')); - this._chromeConnection.on('error', e => this.terminateSession('Debug connection error: ' + e)); return this._chromeConnection.attach(address, port, targetUrl) + .then(() => { + this._chromeConnection.on('Debugger.paused', params => this.onDebuggerPaused(params)); + this._chromeConnection.on('Debugger.resumed', () => this.onDebuggerResumed()); + this._chromeConnection.on('Debugger.scriptParsed', params => this.onScriptParsed(params)); + this._chromeConnection.on('Debugger.globalObjectCleared', () => this.onGlobalObjectCleared()); + this._chromeConnection.on('Debugger.breakpointResolved', params => this.onBreakpointResolved(params)); + + this._chromeConnection.on('Runtime.consoleAPICalled', params => this.onConsoleMessage(params)); + + this._chromeConnection.on('Inspector.detached', () => this.terminateSession('Debug connection detached')); + this._chromeConnection.on('close', () => this.terminateSession('Debug connection closed')); + this._chromeConnection.on('error', e => this.terminateSession('Debug connection error: ' + e)); + + return Promise.all([ + this.chrome.Debugger.enable(), + this.chrome.Runtime.enable(), + this._chromeConnection.run() + ]); + }) .then(() => this.sendInitializedEvent()); } else { return Promise.resolve(); @@ -258,9 +270,9 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { this.clearTargetContext(); } - protected onDebuggerPaused(notification: Chrome.Debugger.PausedParams): void { + protected onDebuggerPaused(notification: Crdp.Debugger.PausedEvent): void { this._exception = undefined; - this._overlayHelper.doAndCancel(() => this._chromeConnection.page_configureOverlay(ChromeDebugAdapter.PAGE_PAUSE_MESSAGE)); + this.setOverlay(ChromeDebugAdapter.PAGE_PAUSE_MESSAGE); this._currentStack = notification.callFrames; // We can tell when we've broken on an exception. Otherwise if hitBreakpoints is set, assume we hit a @@ -286,6 +298,10 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { .then(sendStoppedEvent, sendStoppedEvent); } + private setOverlay(msg: string): void { + this._overlayHelper.doAndCancel(() => this.chrome.Page.configureOverlay({ message: ChromeDebugAdapter.PAGE_PAUSE_MESSAGE }).catch(() => { })); + } + private stopReasonText(reason: string): string { const comment = ['https://github.com/Microsoft/vscode/issues/4568']; switch (reason) { @@ -309,7 +325,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } protected onDebuggerResumed(): void { - this._overlayHelper.wait(() => this._chromeConnection.page_configureOverlay(undefined)); + this.setOverlay(undefined); this._currentStack = null; if (!this._expectingResumedEvent) { @@ -320,7 +336,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } } - protected onScriptParsed(script: Chrome.Debugger.Script): void { + protected onScriptParsed(script: Crdp.Debugger.ScriptParsedEvent): void { // Totally ignore extension scripts, internal Chrome scripts, and so on if (this.shouldIgnoreScript(script)) { return; @@ -354,7 +370,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - protected onBreakpointResolved(params: Chrome.Debugger.BreakpointResolvedParams): void { + protected onBreakpointResolved(params: Crdp.Debugger.BreakpointResolvedEvent): void { const script = this._scriptsById.get(params.location.scriptId); if (!script) { // Breakpoint resolved for a script we don't know about @@ -377,7 +393,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { this._session.sendEvent(new BreakpointEvent('new', bp)); } - protected onConsoleMessage(params: Chrome.Runtime.ConsoleAPICalledParams): void { + protected onConsoleMessage(params: Crdp.Runtime.ConsoleAPICalledEvent): void { const formattedMessage = formatConsoleMessage(params); if (formattedMessage) { this._session.sendEvent(new OutputEvent( @@ -417,7 +433,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { const setBreakpointsPTimeout = utils.promiseTimeout(setBreakpointsPFailOnError, ChromeDebugAdapter.SET_BREAKPOINTS_TIMEOUT, 'Set breakpoints request timed out'); - // Do just one setBreakpointsRequest at a time to avoid interleaving breakpoint removed/breakpoint added requests to Chrome. + // Do just one setBreakpointsRequest at a time to avoid interleaving breakpoint removed/breakpoint added requests to Crdp. // Swallow errors in the promise queue chain so it doesn't get blocked, but return the failing promise for error handling. this._setBreakpointsRequestQ = setBreakpointsPTimeout.catch(() => undefined); return setBreakpointsPTimeout.then(body => { @@ -477,8 +493,8 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { // but there is a chrome bug where when removing 5+ or so breakpoints at once, it gets into a weird // state where later adds on the same line will fail with 'breakpoint already exists' even though it // does not break there. - return this._committedBreakpointsByUrl.get(url).reduce((p, bpId) => { - return p.then(() => this._chromeConnection.debugger_removeBreakpoint(bpId)).then(() => { }); + return this._committedBreakpointsByUrl.get(url).reduce((p, breakpointId) => { + return p.then(() => this.chrome.Debugger.removeBreakpoint({ breakpointId })).then(() => { }); }, Promise.resolve()).then(() => { this._committedBreakpointsByUrl.set(url, null); }); @@ -489,12 +505,12 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { * Responses from setBreakpointByUrl are transformed to look like the response from setBreakpoint, so they can be * handled the same. */ - protected addBreakpoints(url: string, breakpoints: DebugProtocol.SourceBreakpoint[]): Promise { - let responsePs: Promise[]; + protected addBreakpoints(url: string, breakpoints: DebugProtocol.SourceBreakpoint[]): Promise { + let responsePs: Promise[]; if (url.startsWith(ChromeDebugAdapter.PLACEHOLDER_URL_PROTOCOL)) { // eval script with no real url - use debugger_setBreakpoint - const scriptId = utils.lstrip(url, ChromeDebugAdapter.PLACEHOLDER_URL_PROTOCOL); - responsePs = breakpoints.map(({ line, column = 0, condition }, i) => this._chromeConnection.debugger_setBreakpoint({ scriptId, lineNumber: line, columnNumber: column }, condition)); + const scriptId: Crdp.Runtime.ScriptId = utils.lstrip(url, ChromeDebugAdapter.PLACEHOLDER_URL_PROTOCOL); + responsePs = breakpoints.map(({ line, column = 0, condition }, i) => this.chrome.Debugger.setBreakpoint({ location: { scriptId, lineNumber: line, columnNumber: column }, condition })); } else { // script that has a url - use debugger_setBreakpointByUrl so that Chrome will rebind the breakpoint immediately // after refreshing the page. This is the only way to allow hitting breakpoints in code that runs immediately when @@ -502,22 +518,19 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { const script = this._scriptsByUrl.get(url); const urlRegex = utils.pathToRegex(url); responsePs = breakpoints.map(({ line, column = 0, condition }, i) => { - return this._chromeConnection.debugger_setBreakpointByUrlRegex(urlRegex, line, column, condition).then(response => { + return this.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: line, columnNumber: column, condition }).then(result => { // Now convert the response to a SetBreakpointResponse so both response types can be handled the same - const locations = response.result.locations; - return { - id: response.id, - error: response.error, - result: { - breakpointId: response.result.breakpointId, - actualLocation: locations[0] && { - lineNumber: locations[0].lineNumber, - columnNumber: locations[0].columnNumber, - scriptId: script.scriptId - } + const locations = result.locations; + return { + breakpointId: result.breakpointId, + actualLocation: locations[0] && { + lineNumber: locations[0].lineNumber, + columnNumber: locations[0].columnNumber, + scriptId: script.scriptId } }; - }); + }, + err => ({})); // Ignore errors, return an empty object }); } @@ -525,11 +538,11 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return Promise.all(responsePs); } - private chromeBreakpointResponsesToODPBreakpoints(url: string, responses: Chrome.Debugger.SetBreakpointResponse[], requestBps: DebugProtocol.SourceBreakpoint[]): DebugProtocol.Breakpoint[] { + private chromeBreakpointResponsesToODPBreakpoints(url: string, responses: Crdp.Debugger.SetBreakpointResponse[], requestBps: DebugProtocol.SourceBreakpoint[]): DebugProtocol.Breakpoint[] { // Don't cache errored responses const committedBpIds = responses - .filter(response => !response.error) - .map(response => response.result.breakpointId); + .filter(response => !!response.breakpointId) + .map(response => response.breakpointId); // Cache successfully set breakpoint ids from chrome in committedBreakpoints set this._committedBreakpointsByUrl.set(url, committedBpIds); @@ -537,13 +550,10 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { // Map committed breakpoints to DebugProtocol response breakpoints return responses .map((response, i) => { - const id = response.result ? this._breakpointIdHandles.create(response.result.breakpointId) : undefined; - // The output list needs to be the same length as the input list, so map errors to // unverified breakpoints. - if (response.error || !response.result.actualLocation) { + if (!response) { return { - id, verified: false, line: requestBps[i].line, column: requestBps[i].column || 0, @@ -551,16 +561,16 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } return { - id, + id: this._breakpointIdHandles.create(response.breakpointId), verified: true, - line: response.result.actualLocation.lineNumber, - column: response.result.actualLocation.columnNumber + line: response.actualLocation.lineNumber, + column: response.actualLocation.columnNumber }; }); } public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { - let state: string; + let state: 'all' | 'uncaught' | 'none'; if (args.filters.indexOf('all') >= 0) { state = 'all'; } else if (args.filters.indexOf('uncaught') >= 0) { @@ -569,40 +579,40 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { state = 'none'; } - return this._chromeConnection.debugger_setPauseOnExceptions(state) + return this.chrome.Debugger.setPauseOnExceptions({ state }) .then(() => { }); } public continue(): Promise { this._expectingResumedEvent = true; - return this._currentStep = this._chromeConnection.debugger_resume() + return this._currentStep = this.chrome.Debugger.resume() .then(() => { }); } public next(): Promise { this._expectingStopReason = 'step'; this._expectingResumedEvent = true; - return this._currentStep = this._chromeConnection.debugger_stepOver() + return this._currentStep = this.chrome.Debugger.stepOver() .then(() => { }); } public stepIn(): Promise { this._expectingStopReason = 'step'; this._expectingResumedEvent = true; - return this._currentStep = this._chromeConnection.debugger_stepIn() + return this._currentStep = this.chrome.Debugger.stepInto() .then(() => { }); } public stepOut(): Promise { this._expectingStopReason = 'step'; this._expectingResumedEvent = true; - return this._currentStep = this._chromeConnection.debugger_stepOut() + return this._currentStep = this.chrome.Debugger.stepOut() .then(() => { }); } public pause(): Promise { this._expectingStopReason = 'user_request'; - return this._currentStep = this._chromeConnection.debugger_pause() + return this._currentStep = this.chrome.Debugger.pause() .then(() => { }); } @@ -668,7 +678,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { public scopes(args: DebugProtocol.ScopesArguments): IScopesResponseBody { const currentFrame = this._currentStack[args.frameId]; - const scopes = currentFrame.scopeChain.map((scope: Chrome.Debugger.Scope, i: number) => { + const scopes = currentFrame.scopeChain.map((scope: Crdp.Debugger.Scope, i: number) => { // The first scope should include 'this'. Keep the RemoteObject reference for use by the variables request const thisObj = i === 0 && currentFrame.this; const returnValue = i === 0 && currentFrame.returnValue; @@ -703,22 +713,27 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - public propertyDescriptorToVariable(propDesc: Chrome.Runtime.PropertyDescriptor, owningObjectId?: string): Promise { + public propertyDescriptorToVariable(propDesc: Crdp.Runtime.PropertyDescriptor, owningObjectId?: string): Promise { if (propDesc.get) { // Getter const grabGetterValue = 'function remoteFunction(propName) { return this[propName]; }'; - return this._chromeConnection.runtime_callFunctionOn(owningObjectId, grabGetterValue, [{ value: propDesc.name }]).then(response => { - if (response.error) { - logger.error('Error evaluating getter - ' + response.error.toString()); - return { name: propDesc.name, value: response.error.toString(), variablesReference: 0 }; - } else if (response.result.exceptionDetails) { + return this.chrome.Runtime.callFunctionOn({ + objectId: owningObjectId, + functionDeclaration: grabGetterValue, + arguments: [{ value: propDesc.name }] + }).then(response => { + if (response.exceptionDetails) { // Not an error, getter could be `get foo() { throw new Error('bar'); }` - const exceptionDetails = response.result.exceptionDetails; + const exceptionDetails = response.exceptionDetails; logger.log('Exception thrown evaluating getter - ' + JSON.stringify(exceptionDetails.exception)); - return { name: propDesc.name, value: response.result.exceptionDetails.exception.description, variablesReference: 0 }; + return { name: propDesc.name, value: response.exceptionDetails.exception.description, variablesReference: 0 }; } else { - return this.remoteObjectToVariable(propDesc.name, response.result.result); + return this.remoteObjectToVariable(propDesc.name, response.result); } + }, + error => { + logger.error('Error evaluating getter - ' + error.toString()); + return { name: propDesc.name, value: error.toString(), variablesReference: 0 }; }); } else if (propDesc.set) { // setter without a getter, unlikely @@ -736,19 +751,19 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return Promise.all([ // Need to make two requests to get all properties - this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/false, /*accessorPropertiesOnly=*/true, /*generatePreview=*/true), - this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/true, /*accessorPropertiesOnly=*/false, /*generatePreview=*/true) + this.chrome.Runtime.getProperties({ objectId, ownProperties: false, accessorPropertiesOnly: true, generatePreview: true }), + this.chrome.Runtime.getProperties({ objectId, ownProperties: true, accessorPropertiesOnly: false, generatePreview: true }) ]).then(getPropsResponses => { // Sometimes duplicates will be returned - merge all descriptors by name - const propsByName = new Map(); - const internalPropsByName = new Map(); + const propsByName = new Map(); + const internalPropsByName = new Map(); getPropsResponses.forEach(response => { - if (!response.error) { - response.result.result.forEach(propDesc => + if (response) { + response.result.forEach(propDesc => propsByName.set(propDesc.name, propDesc)); - if (response.result.internalProperties) { - response.result.internalProperties.forEach(internalProp => { + if (response.internalProperties) { + response.internalProperties.forEach(internalProp => { internalPropsByName.set(internalProp.name, internalProp); }); } @@ -767,7 +782,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - private internalPropertyDescriptorToVariable(propDesc: Chrome.Runtime.InternalPropertiesDescriptor): Promise { + private internalPropertyDescriptorToVariable(propDesc: Crdp.Runtime.InternalPropertyDescriptor): Promise { return this.remoteObjectToVariable(propDesc.name, propDesc.value); } @@ -793,19 +808,23 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } private getFilteredVariablesForObjectId(objectId: string, getVarsFn: string, filter: string, start: number, count: number): Promise { - return this._chromeConnection.runtime_callFunctionOn(objectId, getVarsFn, [{ value: start }, { value: count }], /*silent=*/true).then(evalResponse => { - if (evalResponse.error) { - return Promise.reject(errors.errorFromEvaluate(evalResponse.error.message)); - } else if (evalResponse.result.exceptionDetails) { - const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.result.exceptionDetails); + return this.chrome.Runtime.callFunctionOn({ + objectId, + functionDeclaration: getVarsFn, + arguments: [{ value: start }, { value: count }], + silent: true + }).then(evalResponse => { + if (evalResponse.exceptionDetails) { + const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.exceptionDetails); return Promise.reject(errors.errorFromEvaluate(errMsg)); } else { // The eval was successful and returned a reference to the array object. Get the props, then filter // out everything except the index names. - return this.getVariablesForObjectId(evalResponse.result.result.objectId, filter) + return this.getVariablesForObjectId(evalResponse.result.objectId, filter) .then(variables => variables.filter(variable => isIndexedPropName(variable.name))); } - }); + }, + error => Promise.reject(errors.errorFromEvaluate(error.message))); } public source(args: DebugProtocol.SourceArguments): Promise { @@ -822,9 +841,9 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } // If not, should have scriptId - return this._chromeConnection.debugger_getScriptSource(handle.scriptId).then(chromeResponse => { + return this.chrome.Debugger.getScriptSource({ scriptId: handle.scriptId }).then(response => { return { - content: chromeResponse.result.scriptSource, + content: response.scriptSource, mimeType: 'text/javascript' }; }); @@ -845,9 +864,9 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { let evalPromise: Promise; if (this.paused) { const callFrameId = this._currentStack[args.frameId].callFrameId; - evalPromise = this._chromeConnection.debugger_evaluateOnCallFrame(callFrameId, args.expression, undefined, undefined, /*silent=*/true); + evalPromise = this.chrome.Debugger.evaluateOnCallFrame({ callFrameId, expression: args.expression, silent: true }); } else { - evalPromise = this._chromeConnection.runtime_evaluate(args.expression, undefined, undefined, undefined, /*silent=*/true); + evalPromise = this.chrome.Runtime.evaluate({ expression: args.expression, silent: true }); } return evalPromise.then(evalResponse => { @@ -884,44 +903,46 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { .then(value => ({ value })); } - public setVariableValue(frameId: string, scopeIndex: number, name: string, value: string): Promise { - let evalResultObject: Chrome.Runtime.RemoteObject; - return this._chromeConnection.debugger_evaluateOnCallFrame(frameId, value, undefined, undefined, /*silent=*/true).then(evalResponse => { - if (evalResponse.error) { - return Promise.reject(errors.errorFromEvaluate(evalResponse.error.message)); - } else if (evalResponse.result.exceptionDetails) { - const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.result.exceptionDetails); + public setVariableValue(callFrameId: string, scopeNumber: number, variableName: string, value: string): Promise { + let evalResultObject: Crdp.Runtime.RemoteObject; + return this.chrome.Debugger.evaluateOnCallFrame({ callFrameId, expression: value, silent: true }).then(evalResponse => { + if (evalResponse.exceptionDetails) { + const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.exceptionDetails); return Promise.reject(errors.errorFromEvaluate(errMsg)); } else { - evalResultObject = evalResponse.result.result; - const newVal = ChromeUtils.remoteObjectToCallArgument(evalResultObject); - return this._chromeConnection.debugger_setVariableValue(frameId, scopeIndex, name, newVal); + evalResultObject = evalResponse.result; + const newValue = ChromeUtils.remoteObjectToCallArgument(evalResultObject); + return this.chrome.Debugger.setVariableValue({ callFrameId, scopeNumber, variableName, newValue }); } - }) + }, + error => Promise.reject(errors.errorFromEvaluate(error.message))) // Temporary, Microsoft/vscode#12019 .then(setVarResponse => ChromeUtils.remoteObjectToValue(evalResultObject).value); } public setPropertyValue(objectId: string, propName: string, value: string): Promise { - return this._chromeConnection.runtime_callFunctionOn(objectId, `function() { return this["${propName}"] = ${value} }`, undefined, /*silent=*/true).then(response => { - if (response.error) { - return Promise.reject(errors.errorFromEvaluate(response.error.message)); - } else if (response.result.exceptionDetails) { - const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.result.exceptionDetails); + const setPropertyValueFn = `function() { return this["${propName}"] = ${value} }`; + return this.chrome.Runtime.callFunctionOn({ + objectId, functionDeclaration: setPropertyValueFn, + silent: true + }).then(response => { + if (response.exceptionDetails) { + const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.exceptionDetails); return Promise.reject(errors.errorFromEvaluate(errMsg)); } else { // Temporary, Microsoft/vscode#12019 - return ChromeUtils.remoteObjectToValue(response.result.result).value; + return ChromeUtils.remoteObjectToValue(response.result).value; } - }); + }, + error => Promise.reject(errors.errorFromEvaluate(error.message))); } - public remoteObjectToVariable(name: string, object: Chrome.Runtime.RemoteObject, stringify = true): Promise { + public remoteObjectToVariable(name: string, object: Crdp.Runtime.RemoteObject, stringify = true): Promise { let value = ''; if (object) { if (object.type === 'object') { - if (object.subtype === 'internal#location') { + if ((object.subtype) === 'internal#location') { // Could format this nicely later, see #110 value = 'internal#location'; } else if (object.subtype === 'null') { @@ -954,7 +975,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - public createFunctionVariable(name: string, object: Chrome.Runtime.RemoteObject): DebugProtocol.Variable { + public createFunctionVariable(name: string, object: Crdp.Runtime.RemoteObject): DebugProtocol.Variable { let value: string; const firstBraceIdx = object.description.indexOf('{'); if (firstBraceIdx >= 0) { @@ -974,7 +995,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }; } - public createObjectVariable(name: string, object: Chrome.Runtime.RemoteObject, stringify?: boolean): Promise { + public createObjectVariable(name: string, object: Crdp.Runtime.RemoteObject, stringify?: boolean): Promise { let value = object.description; let propCountP: Promise; if (object.subtype === 'array' || object.subtype === 'typedarray') { @@ -1022,7 +1043,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return this.getNumPropsByEval(objectId, getNumPropsFn); } - private getArrayNumPropsByPreview(object: Chrome.Runtime.RemoteObject): IPropCount { + private getArrayNumPropsByPreview(object: Crdp.Runtime.RemoteObject): IPropCount { let indexedVariables = 0; let namedVariables = 0; object.preview.properties.forEach(prop => isIndexedPropName(prop.name) ? indexedVariables++ : namedVariables++); @@ -1034,7 +1055,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return this.getNumPropsByEval(objectId, getNumPropsFn); } - private getCollectionNumPropsByPreview(object: Chrome.Runtime.RemoteObject): IPropCount { + private getCollectionNumPropsByPreview(object: Crdp.Runtime.RemoteObject): IPropCount { let indexedVariables = 0; let namedVariables = object.preview.properties.length + 1; // +1 for [[Entries]]; @@ -1042,24 +1063,28 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } private getNumPropsByEval(objectId: string, getNumPropsFn: string): Promise { - return this._chromeConnection.runtime_callFunctionOn(objectId, getNumPropsFn, undefined, /*silent=*/true, /*returnByValue=*/true).then(response => { - if (response.error) { - return Promise.reject(errors.errorFromEvaluate(response.error.message)); - } else if (response.result.exceptionDetails) { - const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.result.exceptionDetails); + return this.chrome.Runtime.callFunctionOn({ + objectId, + functionDeclaration: getNumPropsFn, + silent: true, + returnByValue: true + }).then(response => { + if (response.exceptionDetails) { + const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.exceptionDetails); return Promise.reject(errors.errorFromEvaluate(errMsg)); } else { - const resultProps = response.result.result.value; + const resultProps = response.result.value; if (resultProps.length !== 2) { return Promise.reject(errors.errorFromEvaluate("Did not get expected props, got " + JSON.stringify(resultProps))); } return { indexedVariables: resultProps[0], namedVariables: resultProps[1] }; } - }); + }, + error => Promise.reject(errors.errorFromEvaluate(error.message))); } - private shouldIgnoreScript(script: Chrome.Debugger.Script): boolean { - return script.isContentScript || script.isInternalScript || script.url.startsWith('extensions::') || script.url.startsWith('chrome-extension://'); + private shouldIgnoreScript(script: Crdp.Debugger.ScriptParsedEvent): boolean { + return script.url.startsWith('extensions::') || script.url.startsWith('chrome-extension://'); } } diff --git a/src/chrome/chromeDebugProtocol.d.ts b/src/chrome/chromeDebugProtocol.d.ts deleted file mode 100644 index 4ca5c7bdc..000000000 --- a/src/chrome/chromeDebugProtocol.d.ts +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Chrome Debugging Protocol - documented at - * https://developer.chrome.com/devtools/docs/protocol/1.1/index - */ -export interface Notification { - method: string; - params: any; -} - -export interface Request { - id: number; - method: string; - params?: any; -} - -export interface Response { - id: number; - error?: any; - result?: any; -} - -export namespace Debugger { - export type ScriptId = string; - export type BreakpointId = string; - - export interface Script { - scriptId: ScriptId; - url: string; - - startLine?: number; - startColumn?: number; - endLine?: number; - endColumn?: number; - isInternalScript?: boolean; - sourceMapURL?: string; - isContentScript?: boolean; - } - - export interface CallFrame { - callFrameId: string; - functionName: string; - location: Location; - scopeChain: Scope[]; - this: Runtime.RemoteObject; - returnValue?: Runtime.RemoteObject; - } - - export interface Scope { - object: Runtime.RemoteObject; - type: string; - } - - export interface PausedParams { - callFrames: CallFrame[]; - // 'exception' or 'other' - reason: string; - data: Runtime.RemoteObject; - hitBreakpoints: BreakpointId[]; - } - - export interface BreakpointResolvedParams { - breakpointId: BreakpointId; - location: Location; - } - - export interface Location { - scriptId: ScriptId; - lineNumber: number; - columnNumber?: number; - } - - export interface SetBreakpointParams { - location: Location; - condition?: string; - } - - export interface SetBreakpointResponse extends Response { - result: { - breakpointId: BreakpointId; - actualLocation: Location; - }; - } - - export interface SetBreakpointByUrlParams { - url?: string; - urlRegex?: string; - lineNumber: number; - columnNumber: number; - condition?: string; - } - - export interface SetBreakpointByUrlResponse extends Response { - result: { - breakpointId: BreakpointId; - locations: Location[]; - }; - } - - export interface RemoveBreakpointParams { - breakpointId: BreakpointId; - } - - export interface EvaluateOnCallFrameParams { - callFrameId: string; - expression: string; - objectGroup: string; - returnByValue: boolean; - } - - export interface ExceptionStackFrame extends Location { - functionName: string; - scriptId: ScriptId; - url: string; - } - - export interface EvaluateOnCallFrameResponse extends Response { - result: { - result: Runtime.RemoteObject; - wasThrown: boolean; - exceptionDetails?: { - text: string; - url: string; - line: number; - column: number; - stackTrace: ExceptionStackFrame[]; - }; - }; - } - - export interface SetPauseOnExceptionsParams { - state: string; - } - - export interface GetScriptSourceParams { - scriptId: ScriptId; - } - - export interface GetScriptSourceResponse extends Response { - result: { - scriptSource: string; - }; - } - - export interface SetVariableParams { - scopeNumber: number; - variableName: string; - newValue: Runtime.CallArgument; - callFrameId: string; - } - - export type SetVariableResponse = Response; -} - -export namespace Runtime { - export interface GetPropertiesParams { - objectId: string; - ownProperties: boolean; - accessorPropertiesOnly: boolean; - } - - export interface GetPropertiesResponse extends Response { - result: { - result: PropertyDescriptor[]; - internalProperties: InternalPropertiesDescriptor[]; - }; - } - - export interface InternalPropertiesDescriptor { - name: string; - value?: RemoteObject; - } - - export interface PropertyDescriptor extends InternalPropertiesDescriptor { - configurable: boolean; - enumerable: boolean; - get?: RemoteObject; - set?: RemoteObject; - wasThrown?: boolean; - writeable?: boolean; - } - - export interface RemoteObject { - className?: string; - description?: string; - objectId?: string; - subtype?: string; - type: string; - value?: any; - unserializableValue?: UnserializableValue; - preview?: ObjectPreview; - } - - export interface PropertyPreview { - name: string; - type: string; - subtype?: string; - value: string; - } - - export interface EntryPreview { - key?: ObjectPreview; - value: ObjectPreview; - } - - export interface ObjectPreview { - type: string; - description: string; - overflow: boolean; - properties: PropertyPreview[]; - entries?: EntryPreview[]; - } - - export interface EvaluateParams { - expression: string; - objectGroup?: string; - contextId?: number; - returnByValue?: boolean; - silent?: boolean; - } - - export interface EvaluateResponse extends Response { - result: { - result: Runtime.RemoteObject; - exceptionDetails: any; - }; - } - - export interface CallFrame { - lineNumber: number; - columnNumber: number; - functionName: string; - scriptId: Debugger.ScriptId; - url: string; - } - - export interface StackTrace { - description?: string; - callFrames: CallFrame[]; - parent?: StackTrace; - } - - /** - * Represents function call argument. Either remote object id objectId, primitive value, - * unserializable primitive value or neither of (for undefined) them should be specified. - */ - export interface CallArgument { - value?: any; - unserializableValue?: UnserializableValue; - objectId?: string; - } - - export interface CallFunctionOnResponse extends Response { - result: { - result: RemoteObject; - exceptionDetails: any; - }; - } - - export type UnserializableValue = "Infinity" | "NaN" | "-Infinity" | "-0"; - - export interface ConsoleAPICalledParams { - type: string; - args: RemoteObject[]; - executionContextId: number; - timestamp: number; - stackTrace?: StackTrace; - } -} - -export namespace Page { - export interface SetOverlayMessageRequest extends Request { - message: string; - } -} - -export namespace Emulation { - interface SetDeviceMetricsOverrideParams { - width: number; - height: number; - deviceScaleFactor: number; - mobile: boolean; - fitWindow: boolean; - scale?: number; - offsetX?: number; - offsetY?: number; - screenWidth?: number; - screenHeight?: number; - positionX?: number; - positionY?: number; - screenOrientation?: ScreenOrientation; - } - - interface ScreenOrientation { - type: string; - angle: number; - } -} - -export namespace Console { - export interface MessageAddedParams { - message: Message; - } - - export interface Message { - line?: number; - column?: number; - - // 'debug', 'error', 'log', 'warning' - level: string; - - // 'assert', 'clear', 'dir', 'dirxml', 'endGroup', 'log', 'profile', 'profileEnd', - // 'startGroup', 'startGroupCollapsed', 'table', 'timing', 'trace' - type?: string; - - parameters?: Runtime.RemoteObject[]; - repeatCount?: string; - stack?: Runtime.StackTrace; - text: string; - url?: string; - source?: string; - timestamp?: number; - executionContextId?: number; - } -} - -export interface ITarget { - description: string; - devtoolsFrontendUrl: string; - id: string; - thumbnailUrl?: string; - title: string; - type: string; - url?: string; - webSocketDebuggerUrl: string; -} \ No newline at end of file diff --git a/src/chrome/chromeTargetDiscoveryStrategy.ts b/src/chrome/chromeTargetDiscoveryStrategy.ts index af2021079..a977fb2fc 100644 --- a/src/chrome/chromeTargetDiscoveryStrategy.ts +++ b/src/chrome/chromeTargetDiscoveryStrategy.ts @@ -6,9 +6,8 @@ import * as logger from '../logger'; import * as utils from '../utils'; import * as chromeUtils from './chromeUtils'; -import * as Chrome from './chromeDebugProtocol'; -import {ITargetDiscoveryStrategy, ITargetFilter} from './chromeConnection'; +import {ITargetDiscoveryStrategy, ITargetFilter, ITarget} from './chromeConnection'; export const getChromeTargetWebSocketURL: ITargetDiscoveryStrategy = (address: string, port: number, targetFilter?: ITargetFilter, targetUrl?: string): Promise => { // Take the custom targetFilter, default to taking all targets @@ -29,14 +28,14 @@ export const getChromeTargetWebSocketURL: ITargetDiscoveryStrategy = (address: s }); }; -function _getTargets(address: string, port: number, targetFilter: ITargetFilter): Promise { +function _getTargets(address: string, port: number, targetFilter: ITargetFilter): Promise { const url = `http://${address}:${port}/json`; logger.log(`Discovering targets via ${url}`); return utils.getURL(url).then(jsonResponse => { try { const responseArray = JSON.parse(jsonResponse); if (Array.isArray(responseArray)) { - return (responseArray as Chrome.ITarget[]) + return (responseArray as ITarget[]) .map(target => _fixRemoteUrl(address, target)) // Filter out some targets as specified by the extension .filter(targetFilter); @@ -52,7 +51,7 @@ function _getTargets(address: string, port: number, targetFilter: ITargetFilter) }); } -function _selectTarget(targets: Chrome.ITarget[], targetUrl?: string): Chrome.ITarget { +function _selectTarget(targets: ITarget[], targetUrl?: string): ITarget { if (targetUrl) { // If a url was specified, try to filter to that url const filteredTargets = chromeUtils.getMatchingTargets(targets, targetUrl); @@ -76,7 +75,7 @@ function _selectTarget(targets: Chrome.ITarget[], targetUrl?: string): Chrome.IT return targets[0]; } -function _fixRemoteUrl(remoteAddress: string, target: Chrome.ITarget): Chrome.ITarget { +function _fixRemoteUrl(remoteAddress: string, target: ITarget): ITarget { if (target.webSocketDebuggerUrl) { const replaceAddress = '//' + remoteAddress; target.webSocketDebuggerUrl = target.webSocketDebuggerUrl.replace('//127.0.0.1', replaceAddress); diff --git a/src/chrome/chromeUtils.ts b/src/chrome/chromeUtils.ts index 3e9e61d74..5400f88bd 100644 --- a/src/chrome/chromeUtils.ts +++ b/src/chrome/chromeUtils.ts @@ -4,9 +4,10 @@ import * as url from 'url'; import * as path from 'path'; +import Crdp from 'chrome-remote-debug-protocol'; -import * as Chrome from './chromeDebugProtocol'; import * as utils from '../utils'; +import {ITarget} from './chromeConnection'; /** * Maps a url from target to an absolute local path. @@ -57,7 +58,7 @@ export function targetUrlToClientPath(webRoot: string, aUrl: string): string { * Convert a RemoteObject to a value+variableHandleRef for the client. * TODO - Delete after Microsoft/vscode#12019!! */ -export function remoteObjectToValue(object: Chrome.Runtime.RemoteObject, stringify = true): { value: string, variableHandleRef?: string } { +export function remoteObjectToValue(object: Crdp.Runtime.RemoteObject, stringify = true): { value: string, variableHandleRef?: string } { let value = ''; let variableHandleRef: string; @@ -103,7 +104,7 @@ export function remoteObjectToValue(object: Chrome.Runtime.RemoteObject, stringi * Returns the targets from the given list that match the targetUrl, which may have * wildcards. * Ignores the protocol and is case-insensitive. */ -export function getMatchingTargets(targets: Chrome.ITarget[], targetUrlPattern: string): Chrome.ITarget[] { +export function getMatchingTargets(targets: ITarget[], targetUrlPattern: string): ITarget[] { const standardizeMatch = aUrl => { // Strip file:///, if present aUrl = utils.fileUrlToPath(aUrl).toLowerCase(); @@ -156,7 +157,7 @@ export function compareVariableNames(var1: string, var2: string): number { return var1.localeCompare(var2); } -export function remoteObjectToCallArgument(object: Chrome.Runtime.RemoteObject): Chrome.Runtime.CallArgument { +export function remoteObjectToCallArgument(object: Crdp.Runtime.RemoteObject): Crdp.Runtime.CallArgument { return { objectId: object.objectId, unserializableValue: object.unserializableValue, diff --git a/src/chrome/consoleHelper.ts b/src/chrome/consoleHelper.ts index e803598d6..b69645706 100644 --- a/src/chrome/consoleHelper.ts +++ b/src/chrome/consoleHelper.ts @@ -4,13 +4,13 @@ import * as url from 'url'; import * as ChromeUtils from './chromeUtils'; -import * as Chrome from './chromeDebugProtocol'; +import Crdp from 'chrome-remote-debug-protocol'; -export function formatConsoleMessage(m: Chrome.Runtime.ConsoleAPICalledParams): { text: string, isError: boolean } { +export function formatConsoleMessage(m: Crdp.Runtime.ConsoleAPICalledEvent): { text: string, isError: boolean } { // types: log, debug, info, error, warning, dir, dirxml, table, trace, clear, // startGroup, startGroupCollapsed, endGroup, assert, profile, profileEnd let outputText: string; - switch(m.type) { + switch (m.type) { case 'log': case 'debug': case 'info': @@ -51,7 +51,7 @@ export function formatConsoleMessage(m: Chrome.Runtime.ConsoleAPICalledParams): return { text: outputText, isError }; } -function resolveParams(m: Chrome.Runtime.ConsoleAPICalledParams): string { +function resolveParams(m: Crdp.Runtime.ConsoleAPICalledEvent): string { const textArg = m.args[0]; let text = remoteObjectToString(textArg); m.args.shift(); @@ -92,7 +92,7 @@ function resolveParams(m: Chrome.Runtime.ConsoleAPICalledParams): string { return text; } -function remoteObjectToString(obj: Chrome.Runtime.RemoteObject): string { +function remoteObjectToString(obj: Crdp.Runtime.RemoteObject): string { const result = ChromeUtils.remoteObjectToValue(obj, /*stringify=*/false); if (result.variableHandleRef) { // The DebugProtocol console API doesn't support returning a variable reference, so do our best to @@ -124,7 +124,7 @@ function remoteObjectToString(obj: Chrome.Runtime.RemoteObject): string { return result.value; } -function arrayRemoteObjToString(obj: Chrome.Runtime.RemoteObject): string { +function arrayRemoteObjToString(obj: Crdp.Runtime.RemoteObject): string { if (obj.preview && obj.preview.properties) { let props: string = obj.preview.properties .map(prop => prop.value) @@ -140,7 +140,7 @@ function arrayRemoteObjToString(obj: Chrome.Runtime.RemoteObject): string { } } -function stackTraceToString(stackTrace: Chrome.Runtime.StackTrace): string { +function stackTraceToString(stackTrace: Crdp.Runtime.StackTrace): string { return stackTrace.callFrames .map(frame => { const fnName = frame.functionName || (frame.url ? '(anonymous)' : '(eval)'); diff --git a/src/chrome/variables.ts b/src/chrome/variables.ts index c3671318b..e79069bc6 100644 --- a/src/chrome/variables.ts +++ b/src/chrome/variables.ts @@ -5,7 +5,7 @@ import * as DebugProtocol from 'vscode-debugadapter'; import {ChromeDebugAdapter} from './chromeDebugAdapter'; -import * as Chrome from './chromeDebugProtocol.d'; +import Crdp from 'chrome-remote-debug-protocol'; export interface IVariableContainer { objectId: string; @@ -31,12 +31,12 @@ export class PropertyContainer extends BaseVariableContainer { } export class ScopeContainer extends BaseVariableContainer { - private _thisObj: Chrome.Runtime.RemoteObject; - private _returnValue: Chrome.Runtime.RemoteObject; + private _thisObj: Crdp.Runtime.RemoteObject; + private _returnValue: Crdp.Runtime.RemoteObject; private _frameId: string; private _origScopeIndex: number; - public constructor(frameId: string, origScopeIndex: number, objectId: string, thisObj?: Chrome.Runtime.RemoteObject, returnValue?: Chrome.Runtime.RemoteObject) { + public constructor(frameId: string, origScopeIndex: number, objectId: string, thisObj?: Crdp.Runtime.RemoteObject, returnValue?: Crdp.Runtime.RemoteObject) { super(objectId); this._thisObj = thisObj; this._returnValue = returnValue; @@ -70,7 +70,7 @@ export class ScopeContainer extends BaseVariableContainer { return adapter.setVariableValue(this._frameId, this._origScopeIndex, name, value); } - private insertRemoteObject(adapter: ChromeDebugAdapter, variables: DebugProtocol.Variable[], name: string, obj: Chrome.Runtime.RemoteObject): Promise { + private insertRemoteObject(adapter: ChromeDebugAdapter, variables: DebugProtocol.Variable[], name: string, obj: Crdp.Runtime.RemoteObject): Promise { return adapter.remoteObjectToVariable(name, obj).then(variable => { variables.unshift(variable); return variables; @@ -79,9 +79,9 @@ export class ScopeContainer extends BaseVariableContainer { } export class ExceptionContainer extends PropertyContainer { - protected _exception: Chrome.Runtime.RemoteObject; + protected _exception: Crdp.Runtime.RemoteObject; - protected constructor(objectId: string, exception: Chrome.Runtime.RemoteObject) { + protected constructor(objectId: string, exception: Crdp.Runtime.RemoteObject) { super(exception.objectId); this._exception = exception; } @@ -89,7 +89,7 @@ export class ExceptionContainer extends PropertyContainer { /** * Expand the exception as if it were a Scope */ - public static create(exception: Chrome.Runtime.RemoteObject): ExceptionContainer { + public static create(exception: Crdp.Runtime.RemoteObject): ExceptionContainer { return exception.objectId ? new ExceptionContainer(exception.objectId, exception) : new ExceptionValueContainer(exception); @@ -100,7 +100,7 @@ export class ExceptionContainer extends PropertyContainer { * For when a value is thrown instead of an object */ export class ExceptionValueContainer extends ExceptionContainer { - public constructor(exception: Chrome.Runtime.RemoteObject) { + public constructor(exception: Crdp.Runtime.RemoteObject) { super('EXCEPTION_ID', exception); } @@ -108,7 +108,7 @@ export class ExceptionValueContainer extends ExceptionContainer { * Make up a fake 'Exception' property to hold the thrown value, displayed under the Exception Scope */ public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise { - const excValuePropDescriptor: Chrome.Runtime.PropertyDescriptor = { name: 'Exception', value: this._exception }; + const excValuePropDescriptor: Crdp.Runtime.PropertyDescriptor = { name: 'Exception', value: this._exception }; return adapter.propertyDescriptorToVariable(excValuePropDescriptor) .then(variable => [variable]); } diff --git a/test/chrome/chromeDebugAdapter.test.ts b/test/chrome/chromeDebugAdapter.test.ts index 72c108a16..50d03682b 100644 --- a/test/chrome/chromeDebugAdapter.test.ts +++ b/test/chrome/chromeDebugAdapter.test.ts @@ -7,7 +7,6 @@ import {DebugProtocol} from 'vscode-debugprotocol'; import {getMockLineNumberTransformer, getMockPathTransformer, getMockSourceMapTransformer} from '../mocks/transformerMocks'; import {ISetBreakpointsResponseBody} from '../../src/debugAdapterInterfaces'; -import * as Chrome from '../../src/chrome/chromeDebugProtocol'; import {ChromeConnection} from '../../src/chrome/chromeConnection'; import {LineColTransformer} from '../../src/transformers/lineNumberTransformer'; @@ -124,13 +123,13 @@ suite('ChromeDebugAdapter', () => { mockChromeConnection .setup(x => x.debugger_setBreakpointByUrlRegex(utils.pathToRegex(url), lineNumber, columnNumber, condition)) .returns(location => Promise.resolve( - { id: 0, result: { breakpointId: BP_ID + i, locations: [{ scriptId, lineNumber, columnNumber }] } })) + { id: 0, result: { breakpointId: BP_ID + i, locations: [{ scriptId, lineNumber, columnNumber }] } })) .verifiable(); } else { mockChromeConnection .setup(x => x.debugger_setBreakpoint(It.isAny(), condition)) .returns(location => Promise.resolve( - { id: 0, result: { breakpointId: BP_ID + i, actualLocation: { scriptId, lineNumber, columnNumber } } })) + { id: 0, result: { breakpointId: BP_ID + i, actualLocation: { scriptId, lineNumber, columnNumber } } })) .verifiable(); } }); @@ -140,7 +139,7 @@ suite('ChromeDebugAdapter', () => { indicies.forEach(i => { mockChromeConnection .setup(x => x.debugger_removeBreakpoint(It.isValue(BP_ID + i))) - .returns(() => Promise.resolve({ id: 0 })) + .returns(() => Promise.resolve({ id: 0 })) .verifiable(); }); } @@ -169,7 +168,7 @@ suite('ChromeDebugAdapter', () => { mockSourceMapTransformer.setup(m => m.scriptParsed(It.isValue(undefined), It.isValue(undefined))) .returns(() => Promise.resolve([])); - mockEventEmitter.emit('Debugger.scriptParsed', { scriptId, url }); + mockEventEmitter.emit('Debugger.scriptParsed', { scriptId, url }); } test('When setting one breakpoint, returns the correct result', () => { @@ -252,9 +251,9 @@ suite('ChromeDebugAdapter', () => { .then(response => { expectRemoveBreakpoint([2, 3]); mockEventEmitter.emit('Debugger.globalObjectCleared'); - mockEventEmitter.emit('Debugger.scriptParsed', { scriptId: 'afterRefreshScriptId', url: FILE_NAME }); - mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 2, location: { scriptId: 'afterRefreshScriptId' } }); - mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 3, location: { scriptId: 'afterRefreshScriptId' } }); + mockEventEmitter.emit('Debugger.scriptParsed', { scriptId: 'afterRefreshScriptId', url: FILE_NAME }); + mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 2, location: { scriptId: 'afterRefreshScriptId' } }); + mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 3, location: { scriptId: 'afterRefreshScriptId' } }); breakpoints.push({ line: 321, column: 123 }); expectSetBreakpoint(breakpoints, FILE_NAME, 'afterRefreshScriptId'); @@ -269,7 +268,7 @@ suite('ChromeDebugAdapter', () => { ]; // Set up the mock to return a different location - const location: Chrome.Debugger.Location = { + const location: Crdp.Debugger.Location = { scriptId: SCRIPT_ID, lineNumber: breakpoints[0].line + 10, columnNumber: breakpoints[0].column + 10 }; const expectedResponse: ISetBreakpointsResponseBody = { breakpoints: [{ line: location.lineNumber, column: location.columnNumber, verified: true, id: 1000 }]}; @@ -278,7 +277,7 @@ suite('ChromeDebugAdapter', () => { mockChromeConnection .setup(x => x.debugger_setBreakpointByUrlRegex(expectedRegex, breakpoints[0].line, breakpoints[0].column, undefined)) .returns(() => Promise.resolve( - { id: 0, result: { breakpointId: BP_ID, locations: [location] } })) + { id: 0, result: { breakpointId: BP_ID, locations: [location] } })) .verifiable(); return chromeDebugAdapter.attach(ATTACH_ARGS) diff --git a/test/chrome/chromeUtils.test.ts b/test/chrome/chromeUtils.test.ts index 385bac0bf..9c84c7456 100644 --- a/test/chrome/chromeUtils.test.ts +++ b/test/chrome/chromeUtils.test.ts @@ -163,7 +163,7 @@ suite('ChromeUtils', () => { suite('getMatchingTargets()', () => { const chromeUtils = getChromeUtils(); - function makeTargets(...urls): Chrome.ITarget[] { + function makeTargets(...urls): Crdp.ITarget[] { // Only the url prop is used return urls.map(url => ({ url })); } diff --git a/test/chrome/consoleHelper.test.ts b/test/chrome/consoleHelper.test.ts index 3619ae998..686c0bb43 100644 --- a/test/chrome/consoleHelper.test.ts +++ b/test/chrome/consoleHelper.test.ts @@ -17,7 +17,7 @@ suite('ConsoleHelper', () => { testUtils.removeUnhandledRejectionListener(); }); - function doAssert(params: Chrome.Runtime.ConsoleAPICalledParams, expectedText: string, expectedIsError = false): void { + function doAssert(params: Crdp.Runtime.ConsoleAPICalledParams, expectedText: string, expectedIsError = false): void { assert.deepEqual(ConsoleHelper.formatConsoleMessage(params), { text: expectedText, isError: expectedIsError }); } @@ -69,8 +69,8 @@ namespace Runtime { * @param params - The list of parameters passed to the log function * @param overrideProps - An object of props that the message should have. The rest are filled in with defaults. */ - function makeMockMessage(type: string, args: any[], overrideProps?: any): Chrome.Runtime.ConsoleAPICalledParams { - const message: Chrome.Runtime.ConsoleAPICalledParams = { + function makeMockMessage(type: string, args: any[], overrideProps?: any): Crdp.Runtime.ConsoleAPICalledParams { + const message: Crdp.Runtime.ConsoleAPICalledParams = { type, executionContextId: 2, timestamp: Date.now(), @@ -95,18 +95,18 @@ namespace Runtime { return message; } - export function makeLog(...args: any[]): Chrome.Runtime.ConsoleAPICalledParams { + export function makeLog(...args: any[]): Crdp.Runtime.ConsoleAPICalledParams { return makeMockMessage('log', args); } - export function makeAssert(...args: any[]): Chrome.Runtime.ConsoleAPICalledParams { - const fakeStackTrace: Chrome.Runtime.StackTrace = { + export function makeAssert(...args: any[]): Crdp.Runtime.ConsoleAPICalledParams { + const fakeStackTrace: Crdp.Runtime.StackTrace = { callFrames: [{ url: '/script/a.js', lineNumber: 4, columnNumber: 0, scriptId: '1', functionName: 'myFn' }] }; return makeMockMessage('assert', args, { level: 'error', stackTrace: fakeStackTrace }); } - export function makeNetworkLog(text: string, url: string): Chrome.Runtime.ConsoleAPICalledParams { + export function makeNetworkLog(text: string, url: string): Crdp.Runtime.ConsoleAPICalledParams { return makeMockMessage('log', [text], { source: 'network', url, level: 'error' }); } }