From 3480682fe1cdd90b2c5cc626711cdd24b79f65d3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 4 Aug 2016 18:05:22 -0700 Subject: [PATCH] Using crdp/noice, except for tests --- package.json | 2 + src/chrome/chromeConnection.ts | 137 ++------- src/chrome/chromeDebugAdapter.ts | 204 +++++++------- src/chrome/chromeDebugProtocol.d.ts | 293 -------------------- src/chrome/chromeTargetDiscoveryStrategy.ts | 9 +- src/chrome/chromeUtils.ts | 7 +- src/chrome/consoleHelper.ts | 12 +- test/chrome/chromeDebugAdapter.test.ts | 1 - 8 files changed, 148 insertions(+), 517 deletions(-) delete mode 100644 src/chrome/chromeDebugProtocol.d.ts diff --git a/package.json b/package.json index a485502b2..71adea233 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ "typings": "./lib/src/index", "main": "./out/src/index", "dependencies": { + "noice-json-rpc": "^1.0.0", "source-map": "^0.5.6", "vscode-debugadapter": "^1.11.0", "vscode-debugprotocol": "^1.11.0", "ws": "^1.1.1" }, "devDependencies": { + "chrome-remote-debug-protocol": "^1.1.20160730", "del": "^2.2.0", "gulp": "^3.9.1", "gulp-debug": "^2.1.2", diff --git a/src/chrome/chromeConnection.ts b/src/chrome/chromeConnection.ts index 76302a185..1f1b6ce74 100644 --- a/src/chrome/chromeConnection.ts +++ b/src/chrome/chromeConnection.ts @@ -7,7 +7,20 @@ import {EventEmitter} from 'events'; import * as utils from '../utils'; import * as logger from '../logger'; -import * as Chrome from './chromeDebugProtocol'; + +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; +} interface IMessageWithId { id: number; @@ -134,7 +147,7 @@ class ResReqWebSocket extends EventEmitter { } } -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; /** @@ -142,7 +155,8 @@ export type ITargetDiscoveryStrategy = (address: string, port: number, targetFil */ export class ChromeConnection { private _nextId: number; - private _socket: ResReqWebSocket; + private _socket: WebSocket; + private _client: Client; private _targetFilter: ITargetFilter; private _targetDiscoveryStrategy: ITargetDiscoveryStrategy; @@ -154,25 +168,25 @@ export class ChromeConnection { 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(); } /** * Attach the websocket to the first available tab in the chrome instance with the given remote debugging port number. */ public attach(address = '127.0.0.1', port = 9222, targetUrl?: string): Promise { - return this._attach(address, port, targetUrl) - .then(() => this.sendMessage('Debugger.enable')) - .then(() => this.sendMessage('Runtime.enable')) - .then(() => { }); - } - - private _attach(address: string, port: number, targetUrl?: string): Promise { return utils.retryAsync(() => this._targetDiscoveryStrategy(address, port, this._targetFilter, targetUrl), /*timeoutMs=*/7000, /*intervalDelay=*/200) - .then(wsUrl => this._socket.open(wsUrl)); + .then(wsUrl => { + this._socket = new WebSocket(wsUrl); + this._client = new Client(this._socket); + }); } public close(): void { @@ -182,100 +196,7 @@ export class ChromeConnection { 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_setBreakpointByUrl(url: string, lineNumber: number, columnNumber: number): Promise { - return this.sendMessage('Debugger.setBreakpointByUrl', { url, lineNumber, columnNumber }); - } - - 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): Promise { - return this.sendMessage('Debugger.evaluateOnCallFrame', { callFrameId, expression, objectGroup, returnByValue }); - } - - 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 runtime_getProperties(objectId: string, ownProperties: boolean, accessorPropertiesOnly: boolean): Promise { - return this.sendMessage('Runtime.getProperties', { objectId, ownProperties, accessorPropertiesOnly }); - } - - public runtime_evaluate(expression: string, objectGroup = 'dummyObjectGroup', contextId?: number, returnByValue = false): Promise { - return this.sendMessage('Runtime.evaluate', { expression, objectGroup, contextId, returnByValue }); - } - - public page_setOverlayMessage(message: string): Promise { - return this.sendMessage('Page.setOverlayMessage', { message }); - } - - public page_clearOverlayMessage(): Promise { - return this.sendMessage('Page.setOverlayMessage'); - } - - 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 - }); + this._socket = null; + this._client = null; } } diff --git a/src/chrome/chromeDebugAdapter.ts b/src/chrome/chromeDebugAdapter.ts index a1c6e2ba1..f78efdd83 100644 --- a/src/chrome/chromeDebugAdapter.ts +++ b/src/chrome/chromeDebugAdapter.ts @@ -13,14 +13,14 @@ import * as ChromeUtils from './chromeUtils'; import * as utils from '../utils'; import * as logger from '../logger'; import {formatConsoleMessage} from './consoleHelper'; -import * as Chrome from './chromeDebugProtocol'; +import Crdp from 'chrome-remote-debug-protocol'; import {spawn, ChildProcess} from 'child_process'; import * as path from 'path'; interface IScopeVarHandle { objectId: string; - thisObj?: Chrome.Runtime.RemoteObject; + thisObj?: Crdp.Runtime.RemoteObject; } export class ChromeDebugAdapter implements IDebugAdapter { @@ -31,15 +31,15 @@ export class ChromeDebugAdapter implements IDebugAdapter { private _clientAttached: boolean; private _variableHandles: Handles; - private _currentStack: Chrome.Debugger.CallFrame[]; - private _committedBreakpointsByUrl: Map; + private _currentStack: Crdp.Debugger.CallFrame[]; + private _committedBreakpointsByUrl: Map; private _overlayHelper: utils.DebounceHelper; - private _exceptionValueObject: Chrome.Runtime.RemoteObject; + private _exceptionValueObject: Crdp.Runtime.RemoteObject; private _expectingResumedEvent: boolean; private _setBreakpointsRequestQ: Promise; - private _scriptsById: Map; - private _scriptsByUrl: Map; + private _scriptsById: Map; + private _scriptsByUrl: Map; private _chromeProc: ChildProcess; private _eventHandler: (event: DebugProtocol.Event) => void; @@ -58,11 +58,15 @@ export class ChromeDebugAdapter implements IDebugAdapter { return !!this._currentStack; } + private get chrome(): Crdp.CrdpClient { + return this._chromeConnection.api; + } + private clearTargetContext(): void { - 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.fireEvent(new Event('clearTargetContext')); @@ -192,24 +196,29 @@ export 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()); - this._chromeConnection.on('close', () => this.terminateSession()); - this._chromeConnection.on('error', () => this.terminateSession()); - - return this._chromeConnection.attach(address, port, targetUrl).then( - () => this.fireEvent(new InitializedEvent()), - e => { - this.clearEverything(); - return utils.errP(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()); + this._chromeConnection.on('close', () => this.terminateSession()); + this._chromeConnection.on('error', () => this.terminateSession()); + + return Promise.all([ + this._chromeConnection.api.Debugger.enable(), + this._chromeConnection.api.Runtime.enable()]); + }).then(() => { + this.fireEvent(new InitializedEvent()); + }, + e => { + this.clearEverything(); + return utils.errP(e); + }); } else { return Promise.resolve(); } @@ -228,9 +237,9 @@ export class ChromeDebugAdapter implements IDebugAdapter { this.clearTargetContext(); } - protected onDebuggerPaused(notification: Chrome.Debugger.PausedParams): void { + protected onDebuggerPaused(notification: Crdp.Debugger.PausedEvent): void { - this._overlayHelper.doAndCancel(() => this._chromeConnection.page_setOverlayMessage(ChromeDebugAdapter.PAGE_PAUSE_MESSAGE)); + this._overlayHelper.doAndCancel(() => this.chrome.Page.setOverlayMessage(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 @@ -242,7 +251,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { if (notification.data && this._currentStack.length) { // Insert a scope to wrap the exception object. exceptionText is unused by Code at the moment. const remoteObjValue = ChromeUtils.remoteObjectToValue(notification.data, /*stringify=*/false); - let scopeObject: Chrome.Runtime.RemoteObject; + let scopeObject: Crdp.Runtime.RemoteObject; if (remoteObjValue.variableHandleRef) { // If the remote object is an object (probably an Error), treat the object like a scope. @@ -255,7 +264,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { this._exceptionValueObject = notification.data; } - this._currentStack[0].scopeChain.unshift({ type: 'Exception', object: scopeObject }); + this._currentStack[0].scopeChain.unshift({ type: 'Exception', object: scopeObject }); } } else { reason = (notification.hitBreakpoints && notification.hitBreakpoints.length) ? 'breakpoint' : 'step'; @@ -265,7 +274,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { } protected onDebuggerResumed(): void { - this._overlayHelper.wait(() => this._chromeConnection.page_clearOverlayMessage()); + this._overlayHelper.wait(() => this.chrome.Page.setOverlayMessage('')); this._currentStack = null; if (!this._expectingResumedEvent) { @@ -277,7 +286,7 @@ export 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; @@ -292,7 +301,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { this.fireEvent(new Event('scriptParsed', { scriptUrl: script.url, sourceMapURL: script.sourceMapURL })); } - 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 @@ -304,7 +313,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { this._committedBreakpointsByUrl.set(script.url, committedBps); } - protected onConsoleMessage(params: Chrome.Runtime.ConsoleAPICalledParams): void { + protected onConsoleMessage(params: Crdp.Runtime.ConsoleAPICalledEvent): void { const formattedMessage = formatConsoleMessage(params); if (formattedMessage) { this.fireEvent(new OutputEvent( @@ -344,7 +353,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { const setBreakpointsPTimeout = utils.promiseTimeout(setBreakpointsPFailOnError, /*timeoutMs*/2000, '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; @@ -366,41 +375,42 @@ export 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); }); } - private addBreakpoints(url: string, lines: number[], cols?: number[]): Promise { - let responsePs: Promise[]; + private addBreakpoints(url: string, lines: number[], cols?: number[]): 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 = lines.map((lineNumber, i) => this._chromeConnection.debugger_setBreakpoint({ scriptId, lineNumber, columnNumber: cols ? cols[i] : 0 })); + responsePs = lines.map((lineNumber, i) => { + const location: Crdp.Debugger.Location = { scriptId, lineNumber, columnNumber: cols ? cols[i] : 0 }; + return (this.chrome.Debugger.setBreakpoint({ location }) as Promise) + .catch(e => null); // Ignore failures + }); } else { - // script that has a url - use debugger_setBreakpointByUrl so that Chrome will rebind the breakpoint immediately + // 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 // the page loads. const script = this._scriptsByUrl.get(url); responsePs = lines.map((lineNumber, i) => { - return this._chromeConnection.debugger_setBreakpointByUrl(url, lineNumber, cols ? cols[i] : 0).then(response => { + return (>this.chrome.Debugger.setBreakpointByUrl({ url, lineNumber, columnNumber: cols ? cols[i] : 0 })).then(response => { // 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 = response.locations; + return { + breakpointId: response.breakpointId, + actualLocation: locations[0] && { + lineNumber: locations[0].lineNumber, + columnNumber: locations[0].columnNumber, + scriptId: script.scriptId } }; - }); + }) + .catch(e => null); // Ignore failures (for now?) }); } @@ -408,11 +418,11 @@ export class ChromeDebugAdapter implements IDebugAdapter { return Promise.all(responsePs); } - private chromeBreakpointResponsesToODPBreakpoints(url: string, responses: Chrome.Debugger.SetBreakpointResponse[], requestLines: number[]): DebugProtocol.Breakpoint[] { + private chromeBreakpointResponsesToODPBreakpoints(url: string, responses: Crdp.Debugger.SetBreakpointResponse[], requestLines: number[]): DebugProtocol.Breakpoint[] { // Don't cache errored responses const committedBpIds = responses - .filter(response => !response.error) - .map(response => response.result.breakpointId); + .filter(response => !!response) // errored responses returned null + .map(response => response.breakpointId); // Cache successfully set breakpoint ids from chrome in committedBreakpoints set this._committedBreakpointsByUrl.set(url, committedBpIds); @@ -422,7 +432,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { .map((response, i) => { // 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 || !response.actualLocation) { return { verified: false, line: requestLines[i], @@ -432,14 +442,14 @@ export class ChromeDebugAdapter implements IDebugAdapter { return { 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) { @@ -448,37 +458,31 @@ export class ChromeDebugAdapter implements IDebugAdapter { state = 'none'; } - return this._chromeConnection.debugger_setPauseOnExceptions(state) - .then(() => { }); + return this.chrome.Debugger.setPauseOnExceptions({ state }) as Promise; } public continue(): Promise { this._expectingResumedEvent = true; - return this._chromeConnection.debugger_resume() - .then(() => { }); + return this.chrome.Debugger.resume() as Promise; } public next(): Promise { this._expectingResumedEvent = true; - return this._chromeConnection.debugger_stepOver() - .then(() => { }); + return this.chrome.Debugger.stepOver() as Promise; } public stepIn(): Promise { this._expectingResumedEvent = true; - return this._chromeConnection.debugger_stepIn() - .then(() => { }); + return this.chrome.Debugger.stepInto() as Promise; } public stepOut(): Promise { this._expectingResumedEvent = true; - return this._chromeConnection.debugger_stepOut() - .then(() => { }); + return this.chrome.Debugger.stepOut() as Promise; } public pause(): Promise { - return this._chromeConnection.debugger_pause() - .then(() => { }); + return this.chrome.Debugger.pause() as Promise; } public stackTrace(args: DebugProtocol.StackTraceArguments): IStackTraceResponseBody { @@ -538,7 +542,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { } public scopes(args: DebugProtocol.ScopesArguments): IScopesResponseBody { - const scopes = this._currentStack[args.frameId].scopeChain.map((scope: Chrome.Debugger.Scope, i: number) => { + const scopes = this._currentStack[args.frameId].scopeChain.map((scope: Crdp.Debugger.Scope, i: number) => { const scopeHandle: IScopeVarHandle = { objectId: scope.object.objectId }; if (i === 0) { // The first scope should include 'this'. Keep the RemoteObject reference for use by the variables request @@ -563,22 +567,21 @@ export class ChromeDebugAdapter implements IDebugAdapter { // If this is the special marker for an exception value, create a fake property descriptor so the usual route can be used if (handle.objectId === ChromeDebugAdapter.EXCEPTION_VALUE_ID) { - const excValuePropDescriptor: Chrome.Runtime.PropertyDescriptor = { name: 'exception', value: this._exceptionValueObject }; + const excValuePropDescriptor: Crdp.Runtime.PropertyDescriptor = { name: 'exception', value: this._exceptionValueObject }; return Promise.resolve({ variables: [this.propertyDescriptorToVariable(excValuePropDescriptor)] }); } + const { objectId } = handle; return Promise.all([ // Need to make two requests to get all properties - this._chromeConnection.runtime_getProperties(handle.objectId, /*ownProperties=*/false, /*accessorPropertiesOnly=*/true), - this._chromeConnection.runtime_getProperties(handle.objectId, /*ownProperties=*/true, /*accessorPropertiesOnly=*/false) + this.chrome.Runtime.getProperties({ objectId, ownProperties: false, accessorPropertiesOnly: true }), + this.chrome.Runtime.getProperties({ objectId, ownProperties: true, accessorPropertiesOnly: false }) ]).then(getPropsResponses => { // Sometimes duplicates will be returned - merge all property descriptors returned - const propsByName = new Map(); + const propsByName = new Map(); getPropsResponses.forEach(response => { - if (!response.error) { - response.result.result.forEach(propDesc => - propsByName.set(propDesc.name, propDesc)); - } + response.result.forEach(propDesc => + propsByName.set(propDesc.name, propDesc)); }); // Convert Chrome prop descriptors to DebugProtocol vars, sort the result @@ -596,9 +599,9 @@ export class ChromeDebugAdapter implements IDebugAdapter { } public source(args: DebugProtocol.SourceArguments): Promise { - return this._chromeConnection.debugger_getScriptSource(sourceReferenceToScriptId(args.sourceReference)).then(chromeResponse => { + return (>this.chrome.Debugger.getScriptSource({ scriptId: sourceReferenceToScriptId(args.sourceReference) })).then(chromeResponse => { return { - content: chromeResponse.result.scriptSource, + content: chromeResponse.scriptSource, mimeType: 'text/javascript' }; }); @@ -616,32 +619,31 @@ export class ChromeDebugAdapter implements IDebugAdapter { } public evaluate(args: DebugProtocol.EvaluateArguments): Promise { - let evalPromise: Promise; + let evalPromise: Promise; if (this.paused) { const callFrameId = this._currentStack[args.frameId].callFrameId; - evalPromise = this._chromeConnection.debugger_evaluateOnCallFrame(callFrameId, args.expression); + evalPromise = this.chrome.Debugger.evaluateOnCallFrame({ callFrameId, expression: args.expression }) as Promise; } else { - evalPromise = this._chromeConnection.runtime_evaluate(args.expression); + evalPromise = this.chrome.Runtime.evaluate({ expression: args.expression }) as Promise; } return evalPromise.then(evalResponse => { - if (evalResponse.result.wasThrown) { - const evalResult = evalResponse.result; + if (evalResponse.wasThrown) { let errorMessage = 'Error'; - if (evalResult.exceptionDetails) { - errorMessage = evalResult.exceptionDetails.text; - } else if (evalResult.result && evalResult.result.description) { - errorMessage = evalResult.result.description; + if (evalResponse.exceptionDetails) { + errorMessage = evalResponse.exceptionDetails.text; + } else if (evalResponse.result && evalResponse.result.description) { + errorMessage = evalResponse.result.description; } return utils.errP(errorMessage); } - const { value, variablesReference } = this.remoteObjectToValueWithHandle(evalResponse.result.result); + const { value, variablesReference } = this.remoteObjectToValueWithHandle(evalResponse.result); return { result: value, variablesReference }; }); } - private propertyDescriptorToVariable(propDesc: Chrome.Runtime.PropertyDescriptor): DebugProtocol.Variable { + private propertyDescriptorToVariable(propDesc: Crdp.Runtime.PropertyDescriptor): DebugProtocol.Variable { if (propDesc.get || propDesc.set) { // A property doesn't have a value here, and we shouldn't evaluate the getter because it may have side effects. // Node adapter shows 'undefined', Chrome can eval the getter on demand. @@ -656,7 +658,7 @@ export class ChromeDebugAdapter implements IDebugAdapter { * Run the object through ChromeUtilities.remoteObjectToValue, and if it returns a variableHandle reference, * use it with this instance's variableHandles to create a variable handle. */ - private remoteObjectToValueWithHandle(object: Chrome.Runtime.RemoteObject): { value: string, variablesReference: number } { + private remoteObjectToValueWithHandle(object: Crdp.Runtime.RemoteObject): { value: string, variablesReference: number } { const { value, variableHandleRef } = ChromeUtils.remoteObjectToValue(object); const result = { value, variablesReference: 0 }; if (variableHandleRef) { @@ -666,15 +668,15 @@ export class ChromeDebugAdapter implements IDebugAdapter { return result; } - private shouldIgnoreScript(script: Chrome.Debugger.Script): boolean { + private shouldIgnoreScript(script: Crdp.Debugger.ScriptParsedEvent): boolean { return script.isContentScript || script.isInternalScript || script.url.startsWith('extensions::') || script.url.startsWith('chrome-extension://'); } } -function scriptIdToSourceReference(scriptId: Chrome.Debugger.ScriptId): number { +function scriptIdToSourceReference(scriptId: Crdp.Runtime.ScriptId): number { return parseInt(scriptId, 10); } -function sourceReferenceToScriptId(sourceReference: number): Chrome.Debugger.ScriptId { +function sourceReferenceToScriptId(sourceReference: number): Crdp.Runtime.ScriptId { return '' + sourceReference; } diff --git a/src/chrome/chromeDebugProtocol.d.ts b/src/chrome/chromeDebugProtocol.d.ts deleted file mode 100644 index 3fa509d5a..000000000 --- a/src/chrome/chromeDebugProtocol.d.ts +++ /dev/null @@ -1,293 +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: any; - } - - 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 namespace Runtime { - export interface GetPropertiesParams { - objectId: string; - ownProperties: boolean; - accessorPropertiesOnly: boolean; - } - - export interface GetPropertiesResponse extends Response { - result: { - result: PropertyDescriptor[]; - }; - } - - export interface PropertyDescriptor { - configurable: boolean; - enumerable: boolean; - get?: RemoteObject; - name: string; - set?: RemoteObject; - value?: RemoteObject; - wasThrown?: boolean; - writeable?: boolean; - } - - export interface RemoteObject { - className?: string; - description?: string; - objectId?: string; - subtype?: string; - type: string; - value?: any; - preview?: { - type: string; - description: string; - lossless: boolean; - overflow: boolean; - properties: PropertyPreview[]; - }; - } - - export interface PropertyPreview { - name: string; - type: string; - subtype?: string; - value: string; - } - - export interface EvaluateParams { - expression: string; - objectGroup: string; - contextId: number; - returnByValue: boolean; - } - - export interface EvaluateResponse extends Response { - result: { - result: Runtime.RemoteObject; - wasThrown: boolean; - }; - } - - export interface CallFrame { - lineNumber: number; - columnNumber: number; - functionName: string; - scriptId: Debugger.ScriptId; - url: string; - } - - export interface StackTrace { - description?: string; - callFrames: CallFrame[]; - parent?: StackTrace; - } - - 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 db440e5e3..a7396b71d 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[]) // Filter out some targets as specified by the extension .filter(targetFilter); } @@ -51,7 +50,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); diff --git a/src/chrome/chromeUtils.ts b/src/chrome/chromeUtils.ts index ba22e580f..dd2cf1ca3 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 utils from '../utils'; -import * as Chrome from './chromeDebugProtocol'; +import {ITarget} from './chromeConnection'; /** * Maps a url from target to an absolute local path. @@ -56,7 +57,7 @@ export function targetUrlToClientPath(webRoot: string, aUrl: string): string { /** * Convert a RemoteObject to a value+variableHandleRef for the client. */ -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; @@ -102,7 +103,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(); diff --git a/src/chrome/consoleHelper.ts b/src/chrome/consoleHelper.ts index 1c23f63eb..240fdc4b8 100644 --- a/src/chrome/consoleHelper.ts +++ b/src/chrome/consoleHelper.ts @@ -4,9 +4,9 @@ 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; @@ -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 { } } -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/test/chrome/chromeDebugAdapter.test.ts b/test/chrome/chromeDebugAdapter.test.ts index 4f6cdf8a1..4ff638d45 100644 --- a/test/chrome/chromeDebugAdapter.test.ts +++ b/test/chrome/chromeDebugAdapter.test.ts @@ -5,7 +5,6 @@ import {DebugProtocol} from 'vscode-debugprotocol'; import {ISetBreakpointsResponseBody} from '../../src/debugAdapterInterfaces'; -import * as Chrome from '../../src/chrome/chromeDebugProtocol'; import {ChromeConnection} from '../../src/chrome/chromeConnection'; import * as mockery from 'mockery';