Support running requests and responses through a set of transforms between Code and the Webkit adapter, for testability
This commit is contained in:
Родитель
36c721df26
Коммит
09ceed618c
|
@ -0,0 +1,61 @@
|
||||||
|
/*---------------------------------------------------------
|
||||||
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||||
|
*--------------------------------------------------------*/
|
||||||
|
|
||||||
|
import Utilities = require('../webkit/utilities');
|
||||||
|
|
||||||
|
export type EventHandler = (event: DebugProtocol.Event) => void;
|
||||||
|
|
||||||
|
export class AdapterProxy {
|
||||||
|
public constructor(private _requestTranslators: IDebugTranslator[], private _debugAdapter: IDebugAdapter, private _eventHandler: EventHandler) {
|
||||||
|
this._debugAdapter.registerEventHandler(this._eventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispatchRequest(request: DebugProtocol.Request): Promise<any> {
|
||||||
|
if (!(request.command in this._debugAdapter)) {
|
||||||
|
Promise.reject('unknowncommand');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.translateRequest(request)
|
||||||
|
// Pass the modified args to the adapter
|
||||||
|
.then(() => this._debugAdapter[request.command](request.arguments))
|
||||||
|
|
||||||
|
// Pass the body back through the translators and ensure the body is returned
|
||||||
|
.then((body?) => {
|
||||||
|
return this.translateResponse(request, body)
|
||||||
|
.then(() => body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the request arguments through the translators. They modify the object in place.
|
||||||
|
*/
|
||||||
|
private translateRequest(request: DebugProtocol.Request): Promise<void> {
|
||||||
|
return this._requestTranslators.reduce(
|
||||||
|
(p, translator) => {
|
||||||
|
// If the translator implements this command, give it a chance to modify the args. Otherwise skip it
|
||||||
|
return request.command in translator ?
|
||||||
|
p.then(() => translator[request.command](request.arguments)) :
|
||||||
|
p;
|
||||||
|
}, Promise.resolve<void>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass the response body back through the translators in reverse order. They modify the body in place.
|
||||||
|
*/
|
||||||
|
private translateResponse(request: DebugProtocol.Request, body: any): Promise<void> {
|
||||||
|
if (!body) {
|
||||||
|
return Promise.resolve<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const reversedTranslators = Utilities.reversedArr(this._requestTranslators);
|
||||||
|
return reversedTranslators.reduce(
|
||||||
|
(p, translator) => {
|
||||||
|
// If the translator implements this command, give it a chance to modify the args. Otherwise skip it
|
||||||
|
const bodyTranslateMethodName = request.command + "Response";
|
||||||
|
return bodyTranslateMethodName in translator ?
|
||||||
|
p.then(() => translator[bodyTranslateMethodName](body)) :
|
||||||
|
p;
|
||||||
|
}, Promise.resolve<void>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*---------------------------------------------------------
|
||||||
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||||
|
*--------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts from 1 based lines on the client side to 0 based lines on the target side
|
||||||
|
*/
|
||||||
|
export class LineNumberTranslator {
|
||||||
|
private _targetLinesStartAt1: boolean;
|
||||||
|
private _clientLinesStartAt1: boolean;
|
||||||
|
|
||||||
|
constructor(targetLinesStartAt1: boolean) {
|
||||||
|
this._targetLinesStartAt1 = targetLinesStartAt1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialize(args: IInitializeRequestArgs): void {
|
||||||
|
this._clientLinesStartAt1 = args.linesStartAt1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): void {
|
||||||
|
args.lines = args.lines.map(line => this.convertClientLineToTarget(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
public setBreakpointsResponse(response: SetBreakpointsResponseBody): void {
|
||||||
|
response.breakpoints.forEach(bp => bp.line = this.convertTargetLineToClient(bp.line));
|
||||||
|
}
|
||||||
|
|
||||||
|
public stackTraceResponse(response: StackTraceResponseBody): void {
|
||||||
|
response.stackFrames.forEach(frame => frame.line = this.convertTargetLineToClient(frame.line));
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertClientLineToTarget(line: number): number {
|
||||||
|
if (this._targetLinesStartAt1) {
|
||||||
|
return this._clientLinesStartAt1 ? line : line + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._clientLinesStartAt1 ? line - 1 : line;
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertTargetLineToClient(line: number): number {
|
||||||
|
if (this._targetLinesStartAt1) {
|
||||||
|
return this._clientLinesStartAt1 ? line : line - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._clientLinesStartAt1 ? line + 1 : line;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ var typescript = require('typescript');
|
||||||
var sourcemaps = require('gulp-sourcemaps');
|
var sourcemaps = require('gulp-sourcemaps');
|
||||||
|
|
||||||
var sources = [
|
var sources = [
|
||||||
|
'adapter',
|
||||||
'common',
|
'common',
|
||||||
'node',
|
'node',
|
||||||
'webkit',
|
'webkit',
|
||||||
'typings',
|
'typings',
|
||||||
'mux'
|
|
||||||
].map(function(tsFolder) { return tsFolder + '/**/*.ts'; });
|
].map(function(tsFolder) { return tsFolder + '/**/*.ts'; });
|
||||||
|
|
||||||
var projectConfig = {
|
var projectConfig = {
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
"outDir": "out"
|
"outDir": "out"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"adapter/adapterProxy.ts",
|
||||||
|
"adapter/lineNumberTranslator.ts",
|
||||||
"webkit/openDebugWebKit.ts",
|
"webkit/openDebugWebKit.ts",
|
||||||
"webkit/pathUtilities.ts",
|
"webkit/pathUtilities.ts",
|
||||||
"webkit/sourceMaps.ts",
|
"webkit/sourceMaps.ts",
|
||||||
|
|
|
@ -67,4 +67,11 @@ export class DebounceHelper {
|
||||||
|
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reversedArr(arr: any[]): any[] {
|
||||||
|
return arr.reduce((reversed: any[], x: any) => {
|
||||||
|
reversed.unshift(x);
|
||||||
|
return reversed;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
|
@ -74,3 +74,26 @@ interface IDebugAdapter {
|
||||||
threads(): Promise<ThreadsResponseBody>;
|
threads(): Promise<ThreadsResponseBody>;
|
||||||
evaluate(args: DebugProtocol.EvaluateArguments): Promise<EvaluateResponseBody>;
|
evaluate(args: DebugProtocol.EvaluateArguments): Promise<EvaluateResponseBody>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare type PromiseOrNot<T> = T | Promise<T>;
|
||||||
|
interface IDebugTranslator {
|
||||||
|
initialize?(args: IInitializeRequestArgs): PromiseOrNot<void>;
|
||||||
|
launch?(args: ILaunchRequestArgs): PromiseOrNot<void>;
|
||||||
|
attach?(args: IAttachRequestArgs): PromiseOrNot<void>;
|
||||||
|
setBreakpoints?(args: DebugProtocol.SetBreakpointsArguments): PromiseOrNot<void>;
|
||||||
|
setExceptionBreakpoints?(args: DebugProtocol.SetExceptionBreakpointsArguments): PromiseOrNot<void>;
|
||||||
|
|
||||||
|
stackTrace?(args: DebugProtocol.StackTraceArguments): PromiseOrNot<void>;
|
||||||
|
scopes?(args: DebugProtocol.ScopesArguments): PromiseOrNot<void>;
|
||||||
|
variables?(args: DebugProtocol.VariablesArguments): PromiseOrNot<void>;
|
||||||
|
source?(args: DebugProtocol.SourceArguments): PromiseOrNot<void>;
|
||||||
|
evaluate?(args: DebugProtocol.EvaluateArguments): PromiseOrNot<void>;
|
||||||
|
|
||||||
|
setBreakpointsResponse?(response: SetBreakpointsResponseBody): PromiseOrNot<void>;
|
||||||
|
stackTraceResponse?(response: StackTraceResponseBody): PromiseOrNot<void>;
|
||||||
|
scopesResponse?(response: ScopesResponseBody): PromiseOrNot<void>;
|
||||||
|
variablesResponse?(response: VariablesResponseBody): PromiseOrNot<void>;
|
||||||
|
sourceResponse?(response: SourceResponseBody): PromiseOrNot<void>;
|
||||||
|
threadsResponse?(response: ThreadsResponseBody): PromiseOrNot<void>;
|
||||||
|
evaluateResponse?(response: EvaluateResponseBody): PromiseOrNot<void>;
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
private static THREAD_ID = 1;
|
private static THREAD_ID = 1;
|
||||||
private static PAGE_PAUSE_MESSAGE = 'Paused in Visual Studio Code';
|
private static PAGE_PAUSE_MESSAGE = 'Paused in Visual Studio Code';
|
||||||
|
|
||||||
private _debuggerLinesStartAt1: boolean;
|
|
||||||
private _clientLinesStartAt1: boolean;
|
private _clientLinesStartAt1: boolean;
|
||||||
|
|
||||||
private _clientCWD: string;
|
private _clientCWD: string;
|
||||||
|
@ -46,8 +45,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
|
|
||||||
private _setBreakpointsRequestQ: Promise<any>;
|
private _setBreakpointsRequestQ: Promise<any>;
|
||||||
|
|
||||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
public constructor() {
|
||||||
this._debuggerLinesStartAt1 = debuggerLinesStartAt1;
|
|
||||||
this._variableHandles = new Handles<string>();
|
this._variableHandles = new Handles<string>();
|
||||||
this._overlayHelper = new Utilities.DebounceHelper(/*timeoutMs=*/200);
|
this._overlayHelper = new Utilities.DebounceHelper(/*timeoutMs=*/200);
|
||||||
|
|
||||||
|
@ -74,10 +72,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
this._eventHandler = eventHandler;
|
this._eventHandler = eventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendEvent(event: DebugProtocol.Event): void {
|
|
||||||
this._eventHandler(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public initialize(args: IInitializeRequestArgs): Promise<void> {
|
public initialize(args: IInitializeRequestArgs): Promise<void> {
|
||||||
this._clientLinesStartAt1 = args.linesStartAt1;
|
this._clientLinesStartAt1 = args.linesStartAt1;
|
||||||
if (args.sourceMaps) {
|
if (args.sourceMaps) {
|
||||||
|
@ -145,7 +139,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
|
|
||||||
return this._webKitConnection.attach(port)
|
return this._webKitConnection.attach(port)
|
||||||
.then(
|
.then(
|
||||||
() => this.sendEvent(new InitializedEvent()),
|
() => this._eventHandler(new InitializedEvent()),
|
||||||
e => {
|
e => {
|
||||||
this.clearEverything();
|
this.clearEverything();
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
|
@ -160,7 +154,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
*/
|
*/
|
||||||
private terminateSession(): void {
|
private terminateSession(): void {
|
||||||
if (this._clientAttached) {
|
if (this._clientAttached) {
|
||||||
this.sendEvent(new TerminatedEvent());
|
this._eventHandler(new TerminatedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearEverything();
|
this.clearEverything();
|
||||||
|
@ -188,7 +182,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
this._overlayHelper.doAndCancel(() => this._webKitConnection.page_setOverlayMessage(WebKitDebugAdapter.PAGE_PAUSE_MESSAGE));
|
this._overlayHelper.doAndCancel(() => this._webKitConnection.page_setOverlayMessage(WebKitDebugAdapter.PAGE_PAUSE_MESSAGE));
|
||||||
this._currentStack = notification.callFrames;
|
this._currentStack = notification.callFrames;
|
||||||
const exceptionText = notification.reason === 'exception' ? notification.data.description : undefined;
|
const exceptionText = notification.reason === 'exception' ? notification.data.description : undefined;
|
||||||
this.sendEvent(new StoppedEvent('pause', /*threadId=*/WebKitDebugAdapter.THREAD_ID, exceptionText));
|
this._eventHandler(new StoppedEvent('pause', /*threadId=*/WebKitDebugAdapter.THREAD_ID, exceptionText));
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDebuggerResumed(): void {
|
private onDebuggerResumed(): void {
|
||||||
|
@ -267,7 +261,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
private _addBreakpoints(sourcePath: string, scriptId: WebKitProtocol.Debugger.ScriptId, lines: number[]): Promise<WebKitProtocol.Debugger.SetBreakpointResponse[]> {
|
private _addBreakpoints(sourcePath: string, scriptId: WebKitProtocol.Debugger.ScriptId, lines: number[]): Promise<WebKitProtocol.Debugger.SetBreakpointResponse[]> {
|
||||||
// Adjust lines for sourcemaps, call setBreakpoint for all breakpoints in the script simultaneously
|
// Adjust lines for sourcemaps, call setBreakpoint for all breakpoints in the script simultaneously
|
||||||
const responsePs = lines
|
const responsePs = lines
|
||||||
.map(clientLine => this.convertClientLineToDebugger(clientLine))
|
|
||||||
.map(debuggerLine => {
|
.map(debuggerLine => {
|
||||||
// Sourcemap lines
|
// Sourcemap lines
|
||||||
if (this._sourceMaps) {
|
if (this._sourceMaps) {
|
||||||
|
@ -305,7 +298,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
|
|
||||||
return <DebugProtocol.Breakpoint>{
|
return <DebugProtocol.Breakpoint>{
|
||||||
verified: true,
|
verified: true,
|
||||||
line: this.convertDebuggerLineToClient(line)
|
line: line
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -373,7 +366,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
id: i,
|
id: i,
|
||||||
name: callFrame.functionName || '(eval code)', // anything else?
|
name: callFrame.functionName || '(eval code)', // anything else?
|
||||||
source,
|
source,
|
||||||
line: this.convertDebuggerLineToClient(line),
|
line: line,
|
||||||
column
|
column
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -522,20 +515,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertClientLineToDebugger(line): number {
|
|
||||||
if (this._debuggerLinesStartAt1) {
|
|
||||||
return this._clientLinesStartAt1 ? line : line + 1;
|
|
||||||
}
|
|
||||||
return this._clientLinesStartAt1 ? line - 1 : line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private convertDebuggerLineToClient(line): number {
|
|
||||||
if (this._debuggerLinesStartAt1) {
|
|
||||||
return this._clientLinesStartAt1 ? line : line - 1;
|
|
||||||
}
|
|
||||||
return this._clientLinesStartAt1 ? line + 1 : line;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,28 +6,54 @@ import {Response} from '../common/v8Protocol';
|
||||||
import {DebugSession, ErrorDestination} from '../common/debugSession';
|
import {DebugSession, ErrorDestination} from '../common/debugSession';
|
||||||
import {WebKitDebugAdapter} from './webKitDebugAdapter';
|
import {WebKitDebugAdapter} from './webKitDebugAdapter';
|
||||||
|
|
||||||
|
import {AdapterProxy} from '../adapter/adapterProxy';
|
||||||
|
import {LineNumberTranslator} from '../adapter/lineNumberTranslator';
|
||||||
|
|
||||||
export class WebKitDebugSession extends DebugSession {
|
export class WebKitDebugSession extends DebugSession {
|
||||||
private _debugAdapter: IDebugAdapter;
|
private _adapterProxy: AdapterProxy;
|
||||||
|
|
||||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
public constructor(targetLinesStartAt1: boolean, isServer: boolean = false) {
|
||||||
super(debuggerLinesStartAt1, isServer);
|
super(targetLinesStartAt1, isServer);
|
||||||
|
|
||||||
this._debugAdapter = new WebKitDebugAdapter(debuggerLinesStartAt1, isServer);
|
this._adapterProxy = new AdapterProxy(
|
||||||
this._debugAdapter.registerEventHandler(event => this.sendEvent(event));
|
[new LineNumberTranslator(targetLinesStartAt1)],
|
||||||
|
new WebKitDebugAdapter(),
|
||||||
|
event => this.sendEvent(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overload sendEvent to log
|
||||||
|
*/
|
||||||
public sendEvent(event: DebugProtocol.Event): void {
|
public sendEvent(event: DebugProtocol.Event): void {
|
||||||
console.log(`To client: ${JSON.stringify(event) }`);
|
console.log(`To client: ${JSON.stringify(event) }`);
|
||||||
super.sendEvent(event);
|
super.sendEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overload sendResponse to log
|
||||||
|
*/
|
||||||
|
public sendResponse(response: DebugProtocol.Response): void {
|
||||||
|
console.log(`To client: ${JSON.stringify(response) }`);
|
||||||
|
super.sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a response and a promise to the response body. If the promise is successful, assigns the response body and sends the response.
|
||||||
|
* If the promise fails, sets the appropriate response parameters and sends the response.
|
||||||
|
*/
|
||||||
public sendResponseAsync(response: DebugProtocol.Response, responseP: Promise<any>): void {
|
public sendResponseAsync(response: DebugProtocol.Response, responseP: Promise<any>): void {
|
||||||
responseP.then(
|
responseP.then(
|
||||||
(body) => {
|
(body?) => {
|
||||||
response.body = body;
|
response.body = body;
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
},
|
},
|
||||||
e => {
|
e => {
|
||||||
|
const eStr = e.toString();
|
||||||
|
if (eStr === 'unknowncommand') {
|
||||||
|
this.sendErrorResponse(response, 1014, 'Unrecognized request', null, ErrorDestination.Telemetry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(e.toString());
|
console.log(e.toString());
|
||||||
response.message = e.toString();
|
response.message = e.toString();
|
||||||
response.success = false;
|
response.success = false;
|
||||||
|
@ -35,25 +61,18 @@ export class WebKitDebugSession extends DebugSession {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendResponse(response: DebugProtocol.Response): void {
|
/**
|
||||||
console.log(`To client: ${JSON.stringify(response) }`);
|
* Overload dispatchRequest to dispatch to the adapter proxy instead of debugSession's methods for each request.
|
||||||
super.sendResponse(response);
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
protected dispatchRequest(request: DebugProtocol.Request): void {
|
protected dispatchRequest(request: DebugProtocol.Request): void {
|
||||||
console.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`);
|
|
||||||
|
|
||||||
const response = new Response(request);
|
const response = new Response(request);
|
||||||
try {
|
try {
|
||||||
if (request.command in this._debugAdapter) {
|
console.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`);
|
||||||
this.sendResponseAsync(
|
this.sendResponseAsync(
|
||||||
response,
|
response,
|
||||||
this._debugAdapter[request.command](request.arguments));
|
this._adapterProxy.dispatchRequest(request));
|
||||||
} else {
|
|
||||||
this.sendErrorResponse(response, 1014, "unrecognized request", null, ErrorDestination.Telemetry);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.sendErrorResponse(response, 1104, "exception while processing request (exception: {_exception})", { _exception: e.message }, ErrorDestination.Telemetry);
|
this.sendErrorResponse(response, 1104, 'Exception while processing request (exception: {_exception})', { _exception: e.message }, ErrorDestination.Telemetry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче