Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Commit

Permalink
Implement proactive sourcemap loading - fix #47
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Sep 16, 2016
1 parent b15a9f4 commit b828a71
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 187 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"typings": "./lib/src/index",
"main": "./out/src/index",
"dependencies": {
"globby": "^6.0.0",
"glob": "^7.0.6",
"request-light": "^0.1.0",
"source-map": "^0.5.6",
"vscode-debugadapter": "^1.12.0",
"vscode-debugprotocol": "^1.12.0",
Expand Down
30 changes: 26 additions & 4 deletions src/chrome/chromeDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*--------------------------------------------------------*/

import {DebugProtocol} from 'vscode-debugprotocol';
import {StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent, Handles, ContinuedEvent} from 'vscode-debugadapter';
import {StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent, Handles, ContinuedEvent, BreakpointEvent} from 'vscode-debugadapter';

import {ILaunchRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, IStackTraceResponseBody,
IAttachRequestArgs, IScopesResponseBody, IVariablesResponseBody,
Expand All @@ -23,6 +23,7 @@ import {LineNumberTransformer} from '../transformers/lineNumberTransformer';
import {BasePathTransformer} from '../transformers/basePathTransformer';
import {RemotePathTransformer} from '../transformers/remotePathTransformer';
import {LazySourceMapTransformer} from '../transformers/lazySourceMapTransformer';
import {EagerSourceMapTransformer} from '../transformers/eagerSourceMapTransformer';

import * as path from 'path';

Expand All @@ -39,6 +40,7 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {

private _clientAttached: boolean;
private _variableHandles: Handles<IVariableContainer>;
private _breakpointIdHandles: utils.ReverseHandles<string>;
private _currentStack: Chrome.Debugger.CallFrame[];
private _committedBreakpointsByUrl: Map<string, Chrome.Debugger.BreakpointId[]>;
private _overlayHelper: utils.DebounceHelper;
Expand All @@ -64,10 +66,12 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {

this._chromeConnection = chromeConnection || new ChromeConnection();
this._variableHandles = new Handles<IVariableContainer>();
this._breakpointIdHandles = new utils.ReverseHandles<string>();

this._overlayHelper = new utils.DebounceHelper(/*timeoutMs=*/200);

this._lineNumberTransformer = lineNumberTransformer || new LineNumberTransformer(/*targetLinesStartAt1=*/false);
this._sourceMapTransformer = sourceMapTransformer || new LazySourceMapTransformer();
this._sourceMapTransformer = sourceMapTransformer || new EagerSourceMapTransformer();
this._pathTransformer = pathTransformer || new RemotePathTransformer();

this.clearEverything();
Expand Down Expand Up @@ -308,6 +312,19 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
const committedBps = this._committedBreakpointsByUrl.get(script.url) || [];
committedBps.push(params.breakpointId);
this._committedBreakpointsByUrl.set(script.url, committedBps);

// TODO - Handle bps removed before resolve (can set ID on BP)
// and #38
const bp = <DebugProtocol.Breakpoint>{
id: this._breakpointIdHandles.lookup(params.breakpointId),
verified: true,
line: params.location.lineNumber,
column: params.location.columnNumber
};
const scriptPath = this._pathTransformer.breakpointResolved(bp, script.url);
this._sourceMapTransformer.breakpointResolved(bp, scriptPath);
this._lineNumberTransformer.breakpointResolved(bp);
this.sendEvent(new BreakpointEvent('new', bp));
}

protected onConsoleMessage(params: Chrome.Console.MessageAddedParams): void {
Expand All @@ -326,6 +343,7 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
}

public setBreakpoints(args: ISetBreakpointsArgs, requestSeq: number): Promise<ISetBreakpointsResponseBody> {
// const requestedLines = args.lines;
this._lineNumberTransformer.setBreakpoints(args);
return this._sourceMapTransformer.setBreakpoints(args, requestSeq)
.then(() => this._pathTransformer.setBreakpoints(args))
Expand Down Expand Up @@ -353,8 +371,8 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
// 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 => {
this._lineNumberTransformer.setBreakpointsResponse(body);
this._sourceMapTransformer.setBreakpointsResponse(body, requestSeq);
this._lineNumberTransformer.setBreakpointsResponse(body);
return body;
});
} else {
Expand Down Expand Up @@ -435,17 +453,21 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
// 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) {
return <DebugProtocol.Breakpoint>{
id,
verified: false,
line: requestLines[i],
column: 0
column: 0,
};
}

return <DebugProtocol.Breakpoint>{
id,
verified: true,
line: response.result.actualLocation.lineNumber,
column: response.result.actualLocation.columnNumber
Expand Down
29 changes: 28 additions & 1 deletion src/sourceMaps/sourceMapFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import * as path from 'path';
import * as url from 'url';
import * as fs from 'fs';
import * as os from 'os';
import * as crypto from 'crypto';
const xhr = require('request-light');

import * as sourceMapUtils from './sourceMapUtils';
import * as utils from '../utils';
Expand Down Expand Up @@ -119,4 +122,28 @@ function loadSourceMapContents(mapPathOrURL: string): Promise<string> {
}

return contentsP;
}
}

function downloadSourceMapContents(sourceMapUri: string): Promise<string> {
// use sha256 to ensure the hash value can be used in filenames
const hash = crypto.createHash('sha256').update(sourceMapUri).digest('hex');

const cachePath = path.join(os.tmpdir(), 'com.microsoft.VSCode', 'node-debug2', 'sm-cache');
const sourceMapPath = path.join(cachePath, hash);

const exists = fs.existsSync(sourceMapPath);
if (exists) {
return loadSourceMapContents(sourceMapPath);
}

const options = {
url: sourceMapUri,
followRedirects: 5
};

return xhr.xhr(options)
.then(
response => this._writeFile(path, response.responseText)
.then(() => response.responseText),
error => utils.errP(xhr.getErrorStatusDescription(error.status)));
}
1 change: 1 addition & 0 deletions src/sourceMaps/sourceMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import {URI} from './uri';
import {SourceMap, MappedPosition} from './sourceMap';
import {getMapForGeneratedPath} from './sourceMapFactory';
import {ISourceMapPathOverrides} from '../debugAdapterInterfaces';
Expand Down
94 changes: 94 additions & 0 deletions src/sourceMaps/uri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as Path from 'path';
import * as URL from 'url';

/**
* Helper code copied from vscode-node-debug that's only relevant for SourceMaps right now,
* but should be merged with other url/path handling code, or removed.
*/

function makePathAbsolute(absPath: string, relPath: string): string {
return Path.resolve(Path.dirname(absPath), relPath);
}

export class URI {
private _uri: string;
private _u: URL.Url;

static file(path: string, base?: string) {

if (!Path.isAbsolute(path)) {
path = makePathAbsolute(base, path);
}
if (path[0] === '/') {
path = 'file://' + path;
} else {
path = 'file:///' + path;
}

const u = new URI();
u._uri = path;
try {
u._u = URL.parse(path);
}
catch (e) {
throw new Error(e);
}
return u;
}

static parse(uri: string, base?: string) {

if (uri.indexOf('http:') === 0 || uri.indexOf('https:') === 0 || uri.indexOf('file:') === 0 || uri.indexOf('data:') === 0 ) {
const u = new URI();
u._uri = uri;
try {
u._u = URL.parse(uri);
}
catch (e) {
throw new Error(e);
}
return u;
}
return URI.file(uri, base);
}

constructor() {
}

uri(): string {
return this._uri;
}

isFile(): boolean {
return this._u.protocol === 'file:';
}

filePath(): string {
let path = this._u.path;
if (/^\/[a-zA-Z]\:\//.test(path)) {
path = path.substr(1); // remove additional '/'
}
return path;
}

isData() {
return this._u.protocol === 'data:' && this._uri.indexOf('application/json') > 0 && this._uri.indexOf('base64') > 0;
}

data(): string {
const pos = this._uri.lastIndexOf(',');
if (pos > 0) {
return this._uri.substr(pos+1);
}
return null;
}

isHTTP(): boolean {
return this._u.protocol === 'http:' || this._u.protocol === 'https:';
}
}
6 changes: 6 additions & 0 deletions src/transformers/basePathTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import {DebugProtocol} from 'vscode-debugprotocol';

import {ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody} from '../debugAdapterInterfaces';

/**
Expand Down Expand Up @@ -30,6 +32,10 @@ export class BasePathTransformer {
return scriptPath;
}

public breakpointResolved(bp: DebugProtocol.Breakpoint, scriptPath: string): string {
return scriptPath;
}

public stackTraceResponse(response: IStackTraceResponseBody): void {
// Have a responsibility to clean up the sourceReference here when it's not needed... See #93
response.stackFrames.forEach(frame => {
Expand Down
Loading

0 comments on commit b828a71

Please sign in to comment.