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 sources = [
|
||||
'adapter',
|
||||
'common',
|
||||
'node',
|
||||
'webkit',
|
||||
'typings',
|
||||
'mux'
|
||||
].map(function(tsFolder) { return tsFolder + '/**/*.ts'; });
|
||||
|
||||
var projectConfig = {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"outDir": "out"
|
||||
},
|
||||
"files": [
|
||||
"adapter/adapterProxy.ts",
|
||||
"adapter/lineNumberTranslator.ts",
|
||||
"webkit/openDebugWebKit.ts",
|
||||
"webkit/pathUtilities.ts",
|
||||
"webkit/sourceMaps.ts",
|
||||
|
|
|
@ -67,4 +67,11 @@ export class DebounceHelper {
|
|||
|
||||
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>;
|
||||
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 PAGE_PAUSE_MESSAGE = 'Paused in Visual Studio Code';
|
||||
|
||||
private _debuggerLinesStartAt1: boolean;
|
||||
private _clientLinesStartAt1: boolean;
|
||||
|
||||
private _clientCWD: string;
|
||||
|
@ -46,8 +45,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
|
||||
private _setBreakpointsRequestQ: Promise<any>;
|
||||
|
||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||
this._debuggerLinesStartAt1 = debuggerLinesStartAt1;
|
||||
public constructor() {
|
||||
this._variableHandles = new Handles<string>();
|
||||
this._overlayHelper = new Utilities.DebounceHelper(/*timeoutMs=*/200);
|
||||
|
||||
|
@ -74,10 +72,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
this._eventHandler = eventHandler;
|
||||
}
|
||||
|
||||
private sendEvent(event: DebugProtocol.Event): void {
|
||||
this._eventHandler(event);
|
||||
}
|
||||
|
||||
public initialize(args: IInitializeRequestArgs): Promise<void> {
|
||||
this._clientLinesStartAt1 = args.linesStartAt1;
|
||||
if (args.sourceMaps) {
|
||||
|
@ -145,7 +139,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
|
||||
return this._webKitConnection.attach(port)
|
||||
.then(
|
||||
() => this.sendEvent(new InitializedEvent()),
|
||||
() => this._eventHandler(new InitializedEvent()),
|
||||
e => {
|
||||
this.clearEverything();
|
||||
return Promise.reject(e);
|
||||
|
@ -160,7 +154,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
*/
|
||||
private terminateSession(): void {
|
||||
if (this._clientAttached) {
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
this._eventHandler(new TerminatedEvent());
|
||||
}
|
||||
|
||||
this.clearEverything();
|
||||
|
@ -188,7 +182,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
this._overlayHelper.doAndCancel(() => this._webKitConnection.page_setOverlayMessage(WebKitDebugAdapter.PAGE_PAUSE_MESSAGE));
|
||||
this._currentStack = notification.callFrames;
|
||||
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 {
|
||||
|
@ -267,7 +261,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
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
|
||||
const responsePs = lines
|
||||
.map(clientLine => this.convertClientLineToDebugger(clientLine))
|
||||
.map(debuggerLine => {
|
||||
// Sourcemap lines
|
||||
if (this._sourceMaps) {
|
||||
|
@ -305,7 +298,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
|
||||
return <DebugProtocol.Breakpoint>{
|
||||
verified: true,
|
||||
line: this.convertDebuggerLineToClient(line)
|
||||
line: line
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -373,7 +366,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
id: i,
|
||||
name: callFrame.functionName || '(eval code)', // anything else?
|
||||
source,
|
||||
line: this.convertDebuggerLineToClient(line),
|
||||
line: line,
|
||||
column
|
||||
};
|
||||
});
|
||||
|
@ -522,20 +515,6 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
|
||||
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 {WebKitDebugAdapter} from './webKitDebugAdapter';
|
||||
|
||||
import {AdapterProxy} from '../adapter/adapterProxy';
|
||||
import {LineNumberTranslator} from '../adapter/lineNumberTranslator';
|
||||
|
||||
export class WebKitDebugSession extends DebugSession {
|
||||
private _debugAdapter: IDebugAdapter;
|
||||
private _adapterProxy: AdapterProxy;
|
||||
|
||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||
super(debuggerLinesStartAt1, isServer);
|
||||
public constructor(targetLinesStartAt1: boolean, isServer: boolean = false) {
|
||||
super(targetLinesStartAt1, isServer);
|
||||
|
||||
this._debugAdapter = new WebKitDebugAdapter(debuggerLinesStartAt1, isServer);
|
||||
this._debugAdapter.registerEventHandler(event => this.sendEvent(event));
|
||||
this._adapterProxy = new AdapterProxy(
|
||||
[new LineNumberTranslator(targetLinesStartAt1)],
|
||||
new WebKitDebugAdapter(),
|
||||
event => this.sendEvent(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload sendEvent to log
|
||||
*/
|
||||
public sendEvent(event: DebugProtocol.Event): void {
|
||||
console.log(`To client: ${JSON.stringify(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 {
|
||||
responseP.then(
|
||||
(body) => {
|
||||
(body?) => {
|
||||
response.body = body;
|
||||
this.sendResponse(response);
|
||||
},
|
||||
e => {
|
||||
const eStr = e.toString();
|
||||
if (eStr === 'unknowncommand') {
|
||||
this.sendErrorResponse(response, 1014, 'Unrecognized request', null, ErrorDestination.Telemetry);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(e.toString());
|
||||
response.message = e.toString();
|
||||
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) }`);
|
||||
super.sendResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload dispatchRequest to dispatch to the adapter proxy instead of debugSession's methods for each request.
|
||||
*/
|
||||
protected dispatchRequest(request: DebugProtocol.Request): void {
|
||||
console.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`);
|
||||
|
||||
const response = new Response(request);
|
||||
try {
|
||||
if (request.command in this._debugAdapter) {
|
||||
this.sendResponseAsync(
|
||||
response,
|
||||
this._debugAdapter[request.command](request.arguments));
|
||||
} else {
|
||||
this.sendErrorResponse(response, 1014, "unrecognized request", null, ErrorDestination.Telemetry);
|
||||
}
|
||||
console.log(`From client: ${request.command}(${JSON.stringify(request.arguments) })`);
|
||||
this.sendResponseAsync(
|
||||
response,
|
||||
this._adapterProxy.dispatchRequest(request));
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче