UNPKG

vscode-chrome-debug-core

Version:

A library for building VS Code debug adapters for targets that support the Chrome Remote Debug Protocol

266 lines (264 loc) 13.5 kB
"use strict"; /*--------------------------------------------------------- * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const vscode_debugadapter_1 = require("vscode-debugadapter"); const ChromeUtils = require("./chromeUtils"); const utils = require("../utils"); const path = require("path"); const nls = require("vscode-nls"); const errors = require("../errors"); const variables_1 = require("./variables"); let localize = nls.loadMessageBundle(__filename); class StackFrames { constructor() { this._frameHandles = new vscode_debugadapter_1.Handles(); } /** * Clear the currently stored stack frames */ reset() { this._frameHandles.reset(); } /** * Get a stack frame by its id */ getFrame(frameId) { return this._frameHandles.get(frameId); } getStackTrace({ args, scripts, originProvider, scriptSkipper, smartStepper, transformers, pauseEvent }) { return __awaiter(this, void 0, void 0, function* () { let stackFrames = pauseEvent.callFrames.map(frame => this.callFrameToStackFrame(frame, scripts, originProvider)) .concat(this.asyncFrames(pauseEvent.asyncStackTrace, scripts, originProvider)); const totalFrames = stackFrames.length; if (typeof args.startFrame === 'number') { stackFrames = stackFrames.slice(args.startFrame); } if (typeof args.levels === 'number') { stackFrames = stackFrames.slice(0, args.levels); } const stackTraceResponse = { stackFrames, totalFrames }; yield transformers.pathTransformer.stackTraceResponse(stackTraceResponse); yield transformers.sourceMapTransformer.stackTraceResponse(stackTraceResponse); yield Promise.all(stackTraceResponse.stackFrames.map((frame) => __awaiter(this, void 0, void 0, function* () { // Remove isSourceMapped to convert back to DebugProtocol.StackFrame const isSourceMapped = frame.isSourceMapped; delete frame.isSourceMapped; if (!frame.source) { return; } // Apply hints to skipped frames const getSkipReason = reason => localize(0, null, reason); if (frame.source.path && scriptSkipper.shouldSkipSource(frame.source.path)) { frame.source.origin = (frame.source.origin ? frame.source.origin + ' ' : '') + getSkipReason('skipFiles'); frame.source.presentationHint = 'deemphasize'; } else if (!isSourceMapped && (yield smartStepper.shouldSmartStep(frame, transformers.pathTransformer, transformers.sourceMapTransformer))) { // TODO !isSourceMapped is a bit of a hack here frame.source.origin = (frame.source.origin ? frame.source.origin + ' ' : '') + getSkipReason('smartStep'); frame.presentationHint = 'deemphasize'; } // Allow consumer to adjust final path if (frame.source.path && frame.source.sourceReference) { frame.source.path = scripts.realPathToDisplayPath(frame.source.path); } // And finally, remove the fake eval path and fix the name, if it was never resolved to a real path if (frame.source.path && ChromeUtils.isEvalScript(frame.source.path)) { frame.source.path = undefined; frame.source.name = scripts.displayNameForSourceReference(frame.source.sourceReference); } }))); transformers.lineColTransformer.stackTraceResponse(stackTraceResponse); stackTraceResponse.stackFrames.forEach(frame => frame.name = this.formatStackFrameName(frame, args.format)); return stackTraceResponse; }); } getScopes({ args, scripts, transformers, variables, pauseEvent, currentException }) { const currentFrame = this._frameHandles.get(args.frameId); if (!currentFrame || !currentFrame.location || !currentFrame.callFrameId) { throw errors.stackFrameNotValid(); } if (!currentFrame.callFrameId) { return { scopes: [] }; } const currentScript = scripts.getScriptById(currentFrame.location.scriptId); const currentScriptUrl = currentScript && currentScript.url; const currentScriptPath = (currentScriptUrl && transformers.pathTransformer.getClientPathFromTargetPath(currentScriptUrl)) || currentScriptUrl; const scopes = currentFrame.scopeChain.map((scope, i) => { // 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; const variablesReference = variables.createHandle(new variables_1.ScopeContainer(currentFrame.callFrameId, i, scope.object.objectId, thisObj, returnValue)); const resultScope = { name: scope.type.substr(0, 1).toUpperCase() + scope.type.substr(1), variablesReference, expensive: scope.type === 'global' }; if (scope.startLocation && scope.endLocation) { resultScope.column = scope.startLocation.columnNumber; resultScope.line = scope.startLocation.lineNumber; resultScope.endColumn = scope.endLocation.columnNumber; resultScope.endLine = scope.endLocation.lineNumber; } return resultScope; }); if (currentException && this.lookupFrameIndex(args.frameId, pauseEvent) === 0) { scopes.unshift({ name: localize(1, null), variablesReference: variables.createHandle(variables_1.ExceptionContainer.create(currentException)) }); } const scopesResponse = { scopes }; if (currentScriptPath) { transformers.sourceMapTransformer.scopesResponse(currentScriptPath, scopesResponse); transformers.lineColTransformer.scopeResponse(scopesResponse); } return scopesResponse; } mapCallFrame(frame, transformers, scripts, originProvider) { return __awaiter(this, void 0, void 0, function* () { const debuggerCF = this.runtimeCFToDebuggerCF(frame); const stackFrame = this.callFrameToStackFrame(debuggerCF, scripts, originProvider); yield transformers.pathTransformer.fixSource(stackFrame.source); yield transformers.sourceMapTransformer.fixSourceLocation(stackFrame); transformers.lineColTransformer.convertDebuggerLocationToClient(stackFrame); return stackFrame; }); } // We parse stack trace from `formattedException`, source map it and return a new string mapFormattedException(formattedException, transformers) { return __awaiter(this, void 0, void 0, function* () { const exceptionLines = formattedException.split(/\r?\n/); for (let i = 0, len = exceptionLines.length; i < len; ++i) { const line = exceptionLines[i]; const matches = line.match(/^\s+at (.*?)\s*\(?([^ ]+):(\d+):(\d+)\)?$/); if (!matches) continue; const linePath = matches[2]; const lineNum = parseInt(matches[3], 10); const adjustedLineNum = lineNum - 1; const columnNum = parseInt(matches[4], 10); const clientPath = transformers.pathTransformer.getClientPathFromTargetPath(linePath); const mapped = yield transformers.sourceMapTransformer.mapToAuthored(clientPath || linePath, adjustedLineNum, columnNum); if (mapped && mapped.source && utils.isNumber(mapped.line) && utils.isNumber(mapped.column) && utils.existsSync(mapped.source)) { transformers.lineColTransformer.mappedExceptionStack(mapped); exceptionLines[i] = exceptionLines[i].replace(`${linePath}:${lineNum}:${columnNum}`, `${mapped.source}:${mapped.line}:${mapped.column}`); } else if (clientPath && clientPath !== linePath) { const location = { line: adjustedLineNum, column: columnNum }; transformers.lineColTransformer.mappedExceptionStack(location); exceptionLines[i] = exceptionLines[i].replace(`${linePath}:${lineNum}:${columnNum}`, `${clientPath}:${location.line}:${location.column}`); } } return exceptionLines.join('\n'); }); } asyncFrames(stackTrace, scripts, originProvider) { if (stackTrace) { const frames = stackTrace.callFrames .map(frame => this.runtimeCFToDebuggerCF(frame)) .map(frame => this.callFrameToStackFrame(frame, scripts, originProvider)); frames.unshift({ id: this._frameHandles.create(null), name: `[ ${stackTrace.description} ]`, source: undefined, line: undefined, column: undefined, presentationHint: 'label' }); return frames.concat(this.asyncFrames(stackTrace.parent, scripts, originProvider)); } else { return []; } } runtimeCFToDebuggerCF(frame) { return { callFrameId: undefined, scopeChain: undefined, this: undefined, location: { lineNumber: frame.lineNumber, columnNumber: frame.columnNumber, scriptId: frame.scriptId }, url: frame.url, functionName: frame.functionName }; } formatStackFrameName(frame, formatArgs) { let formattedName = frame.name; if (frame.source && formatArgs) { if (formatArgs.module) { formattedName += ` [${frame.source.name}]`; } if (formatArgs.line) { formattedName += ` Line ${frame.line}`; } } return formattedName; } callFrameToStackFrame(frame, scripts, originProvider) { const { location, functionName } = frame; const line = location.lineNumber; const column = location.columnNumber; const script = scripts.getScriptById(location.scriptId); try { // When the script has a url and isn't one we're ignoring, send the name and path fields. PathTransformer will // attempt to resolve it to a script in the workspace. Otherwise, send the name and sourceReference fields. const sourceReference = scripts.getSourceReferenceForScriptId(script.scriptId); const source = { name: path.basename(script.url), path: script.url, sourceReference, origin: originProvider(script.url) }; // If the frame doesn't have a function name, it's either an anonymous function // or eval script. If its source has a name, it's probably an anonymous function. const frameName = functionName || (script.url ? '(anonymous function)' : '(eval code)'); return { id: this._frameHandles.create(frame), name: frameName, source, line, column }; } catch (e) { // Some targets such as the iOS simulator behave badly and return nonsense callFrames. // In these cases, return a dummy stack frame const evalUnknown = `${ChromeUtils.EVAL_NAME_PREFIX}_Unknown`; return { id: this._frameHandles.create({}), name: evalUnknown, source: { name: evalUnknown, path: evalUnknown }, line, column }; } } /** * Try to lookup the index of the frame with given ID. Returns -1 for async frames and unknown frames. */ lookupFrameIndex(frameId, pauseEvent) { const currentFrame = this._frameHandles.get(frameId); if (!currentFrame || !currentFrame.callFrameId || !pauseEvent) { return -1; } return pauseEvent.callFrames.findIndex(frame => frame.callFrameId === currentFrame.callFrameId); } } exports.StackFrames = StackFrames; //# sourceMappingURL=stackFrames.js.map