vscode-chrome-debug-core
Version:
A library for building VS Code debug adapters for targets that support the Chrome Remote Debug Protocol
1,115 lines • 65.8 kB
JavaScript
"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 chromeConnection_1 = require("./chromeConnection");
const ChromeUtils = require("./chromeUtils");
const variables_1 = require("./variables");
const variables = require("./variables");
const consoleHelper_1 = require("./consoleHelper");
const stoppedEvent_1 = require("./stoppedEvent");
const internalSourceBreakpoint_1 = require("./internalSourceBreakpoint");
const errors = require("../errors");
const utils = require("../utils");
const utils_1 = require("../utils");
const telemetry_1 = require("../telemetry");
const executionTimingsReporter_1 = require("../executionTimingsReporter");
const lineNumberTransformer_1 = require("../transformers/lineNumberTransformer");
const remotePathTransformer_1 = require("../transformers/remotePathTransformer");
const eagerSourceMapTransformer_1 = require("../transformers/eagerSourceMapTransformer");
const fallbackToClientPathTransformer_1 = require("../transformers/fallbackToClientPathTransformer");
const breakOnLoadHelper_1 = require("./breakOnLoadHelper");
const sourceMapUtils = require("../sourceMaps/sourceMapUtils");
const path = require("path");
const nls = require("vscode-nls");
const remoteMapper_1 = require("../remoteMapper");
const breakpoints_1 = require("./breakpoints");
const variablesManager_1 = require("./variablesManager");
const stackFrames_1 = require("./stackFrames");
const scripts_1 = require("./scripts");
const smartStep_1 = require("./smartStep");
const scriptSkipping_1 = require("./scriptSkipping");
let localize = nls.loadMessageBundle(__filename);
class ChromeDebugAdapter {
constructor({ chromeConnection, lineColTransformer, sourceMapTransformer, pathTransformer, targetFilter, breakpoints, scriptContainer }, session) {
this._domains = new Map();
this._waitAfterStep = Promise.resolve();
this._currentStep = Promise.resolve();
this._currentLogMessage = Promise.resolve();
this._pauseOnPromiseRejections = true;
this._promiseRejectExceptionFilterEnabled = false;
this._smartStepCount = 0;
this._earlyScripts = [];
this._initialSourceMapsP = Promise.resolve();
// Queue to synchronize new source loaded and source removed events so that 'remove' script events
// won't be send before the corresponding 'new' event has been sent
this._sourceLoadedQueue = Promise.resolve(null);
// Promises so ScriptPaused events can wait for ScriptParsed events to finish resolving breakpoints
this._scriptIdToBreakpointsAreResolvedDefer = new Map();
this._loadedSourcesByScriptId = new Map();
telemetry_1.telemetry.setupEventHandler(e => session.sendEvent(e));
this._batchTelemetryReporter = new telemetry_1.BatchTelemetryReporter(telemetry_1.telemetry);
this._session = session;
this._chromeConnection = new (chromeConnection || chromeConnection_1.ChromeConnection)(undefined, targetFilter);
this.events = new executionTimingsReporter_1.StepProgressEventsEmitter(this._chromeConnection.events ? [this._chromeConnection.events] : []);
this._scriptContainer = new (scriptContainer || scripts_1.ScriptContainer)();
this._transformers = {
lineColTransformer: new (lineColTransformer || lineNumberTransformer_1.LineColTransformer)(this._session),
sourceMapTransformer: new (sourceMapTransformer || eagerSourceMapTransformer_1.EagerSourceMapTransformer)(this._scriptContainer),
pathTransformer: new (pathTransformer || remotePathTransformer_1.RemotePathTransformer)()
};
this._breakpoints = new (breakpoints || breakpoints_1.Breakpoints)(this, this._chromeConnection);
this._variablesManager = new variablesManager_1.VariablesManager(this._chromeConnection);
this._stackFrames = new stackFrames_1.StackFrames();
this._scriptSkipper = new scriptSkipping_1.ScriptSkipper(this._chromeConnection, this._transformers);
this.clearTargetContext();
}
get columnBreakpointsEnabled() { return this._columnBreakpointsEnabled; }
get breakOnLoadHelper() { return this._breakOnLoadHelper; }
get chrome() {
return this._chromeConnection.api;
}
/**
* @deprecated
*/
get scriptsById() {
return this._scriptContainer.scriptsByIdMap;
}
get committedBreakpointsByUrl() {
return this._breakpoints.committedBreakpointsByUrl;
}
get pathTransformer() { return this._transformers.pathTransformer; }
get sourceMapTransformer() { return this._transformers.sourceMapTransformer; }
get lineColTransformer() { return this._transformers.lineColTransformer; }
get session() { return this._session; }
get originProvider() { return (url) => this.getReadonlyOrigin(url); }
/**
* Called on 'clearEverything' or on a navigation/refresh
*/
clearTargetContext() {
this.sourceMapTransformer.clearTargetContext();
this._scriptContainer.reset();
if (this._breakpoints) {
this._breakpoints.reset();
}
this.pathTransformer.clearTargetContext();
}
/* __GDPR__
"ClientRequest/initialize" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
initialize(args) {
if (args.supportsMapURLToFilePathRequest) {
this._transformers.pathTransformer = new fallbackToClientPathTransformer_1.FallbackToClientPathTransformer(this._session);
}
this._isVSClient = args.clientID === 'visualstudio';
utils.setCaseSensitivePaths(!this._isVSClient);
this.sourceMapTransformer.isVSClient = this._isVSClient;
if (args.pathFormat !== 'path') {
throw errors.pathFormat();
}
if (args.locale) {
localize = nls.config({ locale: args.locale })(__filename);
}
// because session bypasses dispatchRequest
if (typeof args.linesStartAt1 === 'boolean') {
this._clientLinesStartAt1 = args.linesStartAt1;
}
if (typeof args.columnsStartAt1 === 'boolean') {
this._clientColumnsStartAt1 = args.columnsStartAt1;
}
const exceptionBreakpointFilters = [
{
label: localize(0, null),
filter: 'all',
default: false
},
{
label: localize(1, null),
filter: 'uncaught',
default: false
}
];
if (this._promiseRejectExceptionFilterEnabled) {
exceptionBreakpointFilters.push({
label: localize(2, null),
filter: 'promise_reject',
default: false
});
}
// This debug adapter supports two exception breakpoint filters
return {
exceptionBreakpointFilters,
supportsConfigurationDoneRequest: true,
supportsSetVariable: true,
supportsConditionalBreakpoints: true,
supportsCompletionsRequest: true,
supportsHitConditionalBreakpoints: true,
supportsRestartFrame: true,
supportsExceptionInfoRequest: true,
supportsDelayedStackTraceLoading: true,
supportsValueFormattingOptions: true,
supportsEvaluateForHovers: true,
supportsLoadedSourcesRequest: true,
supportsBreakpointLocationsRequest: true
};
}
/* __GDPR__
"ClientRequest/configurationDone" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
configurationDone() {
return Promise.resolve();
}
get breakOnLoadActive() {
return !!this._breakOnLoadHelper;
}
/* __GDPR__
"ClientRequest/launch" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
launch(args, telemetryPropertyCollector) {
return __awaiter(this, void 0, void 0, function* () {
this.commonArgs(args);
if (args.pathMapping) {
for (const urlToMap in args.pathMapping) {
args.pathMapping[urlToMap] = utils.canonicalizeUrl(args.pathMapping[urlToMap]);
}
}
this.sourceMapTransformer.launch(args);
yield this.pathTransformer.launch(args);
if (!args.__restart) {
/* __GDPR__
"debugStarted" : {
"request" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"args" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('debugStarted', { request: 'launch', args: Object.keys(args) });
}
});
}
/* __GDPR__
"ClientRequest/attach" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
attach(args) {
return __awaiter(this, void 0, void 0, function* () {
this._attachMode = true;
this.commonArgs(args);
this.sourceMapTransformer.attach(args);
yield this.pathTransformer.attach(args);
if (!args.port) {
args.port = 9229;
}
/* __GDPR__
"debugStarted" : {
"request" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"args" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('debugStarted', { request: 'attach', args: Object.keys(args) });
yield this.doAttach(args.port, args.url, args.address, args.timeout, args.websocketUrl, args.extraCRDPChannelPort);
});
}
commonArgs(args) {
let logToFile = false;
let logLevel;
if (args.trace === 'verbose') {
logLevel = vscode_debugadapter_1.Logger.LogLevel.Verbose;
logToFile = true;
}
else if (args.trace) {
logLevel = vscode_debugadapter_1.Logger.LogLevel.Warn;
logToFile = true;
}
else {
logLevel = vscode_debugadapter_1.Logger.LogLevel.Warn;
}
let logTimestamps = args.logTimestamps;
// The debug configuration provider should have set logFilePath on the launch config. If not, default to 'true' to use the
// "legacy" log file path from the CDA subclass
const logFilePath = args.logFilePath || logToFile;
vscode_debugadapter_1.logger.setup(logLevel, logFilePath, logTimestamps);
this._launchAttachArgs = args;
// Enable sourcemaps and async callstacks by default
args.sourceMaps = typeof args.sourceMaps === 'undefined' || args.sourceMaps;
args.showAsyncStacks = typeof args.showAsyncStacks === 'undefined' || args.showAsyncStacks;
this._smartStepper = new smartStep_1.SmartStepper(this._launchAttachArgs.smartStep);
if (args.breakOnLoadStrategy && args.breakOnLoadStrategy !== 'off') {
this._breakOnLoadHelper = new breakOnLoadHelper_1.BreakOnLoadHelper(this, args.breakOnLoadStrategy);
}
// Use hasOwnProperty to explicitly permit setting a falsy targetFilter.
if (args.hasOwnProperty('targetFilter')) {
this._chromeConnection.setTargetFilter(args.targetFilter);
}
}
shutdown() {
this._batchTelemetryReporter.finalize();
this._inShutdown = true;
this._session.shutdown();
}
terminateSession(reason, _disconnectArgs, restart) {
return __awaiter(this, void 0, void 0, function* () {
vscode_debugadapter_1.logger.log(`Terminated: ${reason}`);
if (!this._hasTerminated) {
vscode_debugadapter_1.logger.log(`Waiting for any pending steps or log messages.`);
yield this._currentStep;
yield this._currentLogMessage;
vscode_debugadapter_1.logger.log(`Current step and log messages complete`);
/* __GDPR__
"debugStopped" : {
"reason" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('debugStopped', { reason });
this._hasTerminated = true;
if (this._clientAttached || (this._launchAttachArgs && this._launchAttachArgs.noDebug)) {
this._session.sendEvent(new vscode_debugadapter_1.TerminatedEvent(restart));
}
if (this._chromeConnection.isAttached) {
this._chromeConnection.close();
}
}
});
}
/**
* Hook up all connection events
*/
hookConnectionEvents() {
this.chrome.Debugger.on('paused', params => {
/* __GDPR__
"target/notification/onPaused" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
this.runAndMeasureProcessingTime('target/notification/onPaused', () => __awaiter(this, void 0, void 0, function* () {
yield this.onPaused(params);
}));
});
this.chrome.Debugger.on('resumed', () => this.onResumed());
this.chrome.Debugger.on('scriptParsed', params => {
/* __GDPR__
"target/notification/onScriptParsed" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
this.runAndMeasureProcessingTime('target/notification/onScriptParsed', () => {
return this.onScriptParsed(params);
});
});
this.chrome.Console.on('messageAdded', params => this.onMessageAdded(params));
this.chrome.Runtime.on('consoleAPICalled', params => this.onConsoleAPICalled(params));
this.chrome.Runtime.on('exceptionThrown', params => this.onExceptionThrown(params));
this.chrome.Runtime.on('executionContextsCleared', () => this.onExecutionContextsCleared());
this.chrome.Log.on('entryAdded', params => this.onLogEntryAdded(params));
this.chrome.Debugger.on('breakpointResolved', params => this._breakpoints.onBreakpointResolved(params, this._scriptContainer));
this._chromeConnection.onClose(() => this.terminateSession('websocket closed'));
}
runAndMeasureProcessingTime(notificationName, procedure) {
return __awaiter(this, void 0, void 0, function* () {
const startTime = Date.now();
const startTimeMark = process.hrtime();
let properties = {
startTime: startTime.toString()
};
try {
yield procedure();
properties.successful = 'true';
}
catch (e) {
properties.successful = 'false';
properties.exceptionType = 'firstChance';
utils.fillErrorDetails(properties, e);
}
const elapsedTime = utils.calculateElapsedTime(startTimeMark);
properties.timeTakenInMilliseconds = elapsedTime.toString();
// Callers set GDPR annotation
this._batchTelemetryReporter.reportEvent(notificationName, properties);
});
}
/**
* Enable clients and run connection
*/
runConnection() {
return [
this.chrome.Console.enable()
.catch(() => { }),
utils.toVoidP(this.chrome.Debugger.enable()),
this.chrome.Runtime.enable(),
this.chrome.Log.enable()
.catch(() => { }),
this._chromeConnection.run(),
];
}
doAttach(port, targetUrl, address, timeout, websocketUrl, extraCRDPChannelPort) {
return __awaiter(this, void 0, void 0, function* () {
/* __GDPR__FRAGMENT__
"StepNames" : {
"Attach" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.events.emitStepStarted('Attach');
// Client is attaching - if not attached to the chrome target, create a connection and attach
this._clientAttached = true;
if (!this._chromeConnection.isAttached) {
if (websocketUrl) {
yield this._chromeConnection.attachToWebsocketUrl(websocketUrl, extraCRDPChannelPort);
}
else {
yield this._chromeConnection.attach(address, port, targetUrl, timeout, extraCRDPChannelPort);
}
/* __GDPR__FRAGMENT__
"StepNames" : {
"Attach.ConfigureDebuggingSession.Internal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.events.emitStepStarted('Attach.ConfigureDebuggingSession.Internal');
this._port = port;
this.hookConnectionEvents();
/* __GDPR__FRAGMENT__
"StepNames" : {
"Attach.ConfigureDebuggingSession.Target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.events.emitStepStarted('Attach.ConfigureDebuggingSession.Target');
// Make sure debugging domain is enabled before initializing the script skipper
yield Promise.all(this.runConnection());
this._scriptSkipper.init(this._launchAttachArgs.skipFiles, this._launchAttachArgs.skipFileRegExps);
yield this.initSupportedDomains();
const maxDepth = this._launchAttachArgs.showAsyncStacks ? ChromeDebugAdapter.ASYNC_CALL_STACK_DEPTH : 0;
try {
yield this.chrome.Debugger.setAsyncCallStackDepth({ maxDepth });
}
catch (e) {
// Not supported by older runtimes, ignore it.
}
if (this._breakOnLoadHelper) {
this._breakOnLoadHelper.setBrowserVersion((yield this._chromeConnection.version).browser);
}
/* __GDPR__FRAGMENT__
"StepNames" : {
"Attach.ConfigureDebuggingSession.End" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.events.emitStepStarted('Attach.ConfigureDebuggingSession.End');
}
});
}
initSupportedDomains() {
return __awaiter(this, void 0, void 0, function* () {
try {
const domainResponse = yield this.chrome.Schema.getDomains();
domainResponse.domains.forEach(domain => this._domains.set(domain.name, domain));
}
catch (e) {
// If getDomains isn't supported for some reason, skip this
}
});
}
/**
* This event tells the client to begin sending setBP requests, etc. Some consumers need to override this
* to send it at a later time of their choosing.
*/
sendInitializedEvent() {
return __awaiter(this, void 0, void 0, function* () {
// Wait to finish loading sourcemaps from the initial scriptParsed events
if (this._initialSourceMapsP) {
const initialSourceMapsP = this._initialSourceMapsP;
this._initialSourceMapsP = null;
yield initialSourceMapsP;
this._session.sendEvent(new vscode_debugadapter_1.InitializedEvent());
this.events.emitStepCompleted('NotifyInitialized');
yield Promise.all(this._earlyScripts.map(script => this.sendLoadedSourceEvent(script)));
this._earlyScripts = null;
}
});
}
doAfterProcessingSourceEvents(action) {
return this._sourceLoadedQueue = this._sourceLoadedQueue.then(action);
}
/**
* e.g. the target navigated
*/
onExecutionContextsCleared() {
const cachedScriptParsedEvents = Array.from(this._scriptContainer.loadedScripts);
this.clearTargetContext();
return this.doAfterProcessingSourceEvents(() => __awaiter(this, void 0, void 0, function* () {
for (let scriptedParseEvent of cachedScriptParsedEvents) {
this.sendLoadedSourceEvent(scriptedParseEvent, 'removed');
}
}));
}
onPaused(notification, expectingStopReason = this._expectingStopReason) {
return __awaiter(this, void 0, void 0, function* () {
if (notification.asyncCallStackTraceId) {
yield this.chrome.Debugger.pauseOnAsyncCall({ parentStackTraceId: notification.asyncCallStackTraceId });
yield this.chrome.Debugger.resume();
return { didPause: false };
}
this._variablesManager.onPaused();
this._stackFrames.reset();
this._exception = undefined;
this._lastPauseState = { event: notification, expecting: expectingStopReason };
this._currentPauseNotification = notification;
// If break on load is active, we pass the notification object to breakonload helper
// If it returns true, we continue and return
if (this.breakOnLoadActive) {
let shouldContinue = yield this._breakOnLoadHelper.handleOnPaused(notification);
if (shouldContinue) {
this.chrome.Debugger.resume()
.catch(e => {
vscode_debugadapter_1.logger.error('Failed to resume due to exception: ' + e.message);
});
return { didPause: false };
}
}
// We can tell when we've broken on an exception. Otherwise if hitBreakpoints is set, assume we hit a
// breakpoint. If not set, assume it was a step. We can't tell the difference between step and 'break on anything'.
let reason;
let shouldSmartStep = false;
if (notification.reason === 'exception') {
reason = 'exception';
this._exception = notification.data;
}
else if (notification.reason === 'promiseRejection') {
reason = 'promise_rejection';
// After processing smartStep and so on, check whether we are paused on a promise rejection, and should continue past it
if (this._promiseRejectExceptionFilterEnabled && !this._pauseOnPromiseRejections) {
this.chrome.Debugger.resume()
.catch(() => { });
return { didPause: false };
}
this._exception = notification.data;
}
else if (notification.hitBreakpoints && notification.hitBreakpoints.length) {
reason = 'breakpoint';
const result = this._breakpoints.handleHitCountBreakpoints(expectingStopReason, notification.hitBreakpoints);
if (result) {
return result;
}
}
else if (expectingStopReason) {
// If this was a step, check whether to smart step
reason = expectingStopReason;
shouldSmartStep = yield this._shouldSmartStepCallFrame(this._currentPauseNotification.callFrames[0]);
}
else {
reason = 'debugger_statement';
}
this._expectingStopReason = undefined;
if (shouldSmartStep) {
this._smartStepCount++;
yield this.stepIn(false);
return { didPause: false };
}
else {
if (this._smartStepCount > 0) {
vscode_debugadapter_1.logger.log(`SmartStep: Skipped ${this._smartStepCount} steps`);
this._smartStepCount = 0;
}
// Enforce that the stopped event is not fired until we've sent the response to the step that induced it.
// Also with a timeout just to ensure things keep moving
const sendStoppedEvent = () => {
return this._session.sendEvent(new stoppedEvent_1.StoppedEvent2(reason, /*threadId=*/ ChromeDebugAdapter.THREAD_ID, this._exception));
};
yield utils.promiseTimeout(this._currentStep, /*timeoutMs=*/ 300)
.then(sendStoppedEvent, sendStoppedEvent);
return { didPause: true };
}
});
}
/* __GDPR__
"ClientRequest/exceptionInfo" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
exceptionInfo(args) {
return __awaiter(this, void 0, void 0, function* () {
if (args.threadId !== ChromeDebugAdapter.THREAD_ID) {
throw errors.invalidThread(args.threadId);
}
if (this._exception) {
const isError = this._exception.subtype === 'error';
const message = isError ? utils.firstLine(this._exception.description) : (this._exception.description || this._exception.value);
const formattedMessage = message && message.replace(/\*/g, '\\*');
const response = {
exceptionId: this._exception.className || this._exception.type || 'Error',
breakMode: 'unhandled',
details: {
stackTrace: this._exception.description && (yield this._stackFrames.mapFormattedException(this._exception.description, this._transformers)),
message,
formattedDescription: formattedMessage,
typeName: this._exception.subtype || this._exception.type
}
};
return response;
}
else {
throw errors.noStoredException();
}
});
}
onResumed() {
this._currentPauseNotification = null;
if (this._expectingResumedEvent) {
this._expectingResumedEvent = false;
// Need to wait to eval just a little after each step, because of #148
this._waitAfterStep = utils.promiseTimeout(null, 50);
}
else {
let resumedEvent = new vscode_debugadapter_1.ContinuedEvent(ChromeDebugAdapter.THREAD_ID);
this._session.sendEvent(resumedEvent);
}
}
detectColumnBreakpointSupport(scriptId) {
return __awaiter(this, void 0, void 0, function* () {
this._columnBreakpointsEnabled = false; // So it isn't requested multiple times
try {
yield this.chrome.Debugger.getPossibleBreakpoints({
start: { scriptId, lineNumber: 0, columnNumber: 0 },
end: { scriptId, lineNumber: 1, columnNumber: 0 },
restrictToFunction: false
});
this._columnBreakpointsEnabled = true;
}
catch (e) {
this._columnBreakpointsEnabled = false;
}
this.lineColTransformer.columnBreakpointsEnabled = this._columnBreakpointsEnabled;
});
}
getBreakpointsResolvedDefer(scriptId) {
const existingValue = this._scriptIdToBreakpointsAreResolvedDefer.get(scriptId);
if (existingValue) {
return existingValue;
}
else {
const newValue = utils_1.promiseDefer();
this._scriptIdToBreakpointsAreResolvedDefer.set(scriptId, newValue);
return newValue;
}
}
onScriptParsed(script) {
return __awaiter(this, void 0, void 0, function* () {
// The stack trace and hash can be large and the DA doesn't need it.
delete script.stackTrace;
delete script.hash;
const breakpointsAreResolvedDefer = this.getBreakpointsResolvedDefer(script.scriptId);
try {
this.doAfterProcessingSourceEvents(() => __awaiter(this, void 0, void 0, function* () {
if (typeof this._columnBreakpointsEnabled === 'undefined') {
if (!script.url.includes('internal/per_context')) {
yield this.detectColumnBreakpointSupport(script.scriptId);
yield this.sendInitializedEvent();
}
}
if (this._earlyScripts) {
this._earlyScripts.push(script);
}
else {
yield this.sendLoadedSourceEvent(script);
}
}));
if (script.url) {
script.url = utils.fixDriveLetter(script.url);
}
else {
script.url = ChromeDebugAdapter.EVAL_NAME_PREFIX + script.scriptId;
}
this._scriptContainer.add(script);
const mappedUrl = yield this.pathTransformer.scriptParsed(script.url);
const sourceMapsP = this.sourceMapTransformer.scriptParsed(mappedUrl, script.url, script.sourceMapURL).then((sources) => __awaiter(this, void 0, void 0, function* () {
if (this._hasTerminated) {
return undefined;
}
yield this._breakpoints.handleScriptParsed(script, this._scriptContainer, mappedUrl, sources);
yield this._scriptSkipper.resolveSkipFiles(script, mappedUrl, sources);
}));
if (this._initialSourceMapsP) {
this._initialSourceMapsP = Promise.all([this._initialSourceMapsP, sourceMapsP]);
}
yield sourceMapsP;
breakpointsAreResolvedDefer.resolve(); // By now no matter which code path we choose, resolving pending breakpoints should be finished, so trigger the defer
}
catch (exception) {
breakpointsAreResolvedDefer.reject(exception);
}
});
}
sendLoadedSourceEvent(script, loadedSourceEventReason = 'new') {
return __awaiter(this, void 0, void 0, function* () {
const origin = this.getReadonlyOrigin(script.url);
const source = yield this._scriptContainer.scriptToSource(script, origin);
// This is a workaround for an edge bug, see https://github.com/Microsoft/vscode-chrome-debug-core/pull/329
switch (loadedSourceEventReason) {
case 'new':
case 'changed':
if (this._loadedSourcesByScriptId.get(script.scriptId)) {
if (source.sourceReference) {
// We only need to send changed events for dynamic scripts. The client tracks files on storage on it's own, so this notification is not needed
loadedSourceEventReason = 'changed';
}
else {
return; // VS is strict about the changed notifications, and it will fail if we send a changed notification for a file on storage, so we omit it on purpose
}
}
else {
loadedSourceEventReason = 'new';
}
this._loadedSourcesByScriptId.set(script.scriptId, script);
break;
case 'removed':
if (!this._loadedSourcesByScriptId.delete(script.scriptId)) {
telemetry_1.telemetry.reportEvent('LoadedSourceEventError', { issue: 'Tried to remove non-existent script', scriptId: script.scriptId });
return;
}
break;
default:
telemetry_1.telemetry.reportEvent('LoadedSourceEventError', { issue: 'Unknown reason', reason: loadedSourceEventReason });
}
const scriptEvent = new vscode_debugadapter_1.LoadedSourceEvent(loadedSourceEventReason, source);
this._session.sendEvent(scriptEvent);
});
}
/* __GDPR__
"ClientRequest/toggleSmartStep" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
toggleSmartStep() {
return __awaiter(this, void 0, void 0, function* () {
this._smartStepEnabled = !this._smartStepEnabled;
this.onPaused(this._lastPauseState.event, this._lastPauseState.expecting);
});
}
/* __GDPR__
"ClientRequest/toggleSkipFileStatus" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
toggleSkipFileStatus(args) {
return __awaiter(this, void 0, void 0, function* () {
if (args.path) {
args.path = utils.fileUrlToPath(args.path);
args.path = remoteMapper_1.mapRemoteClientToInternalPath(args.path);
}
if (!(yield this.isInCurrentStack(args))) {
// Only valid for files that are in the current stack
const logName = args.path || this._scriptContainer.displayNameForSourceReference(args.sourceReference);
vscode_debugadapter_1.logger.log(`Can't toggle the skipFile status for ${logName} - it's not in the current stack.`);
return;
}
else {
this._scriptSkipper.toggleSkipFileStatus(args, this._scriptContainer, this._transformers);
this.onPaused(this._lastPauseState.event, this._lastPauseState.expecting);
}
});
}
isInCurrentStack(args) {
return __awaiter(this, void 0, void 0, function* () {
const currentStack = yield this.stackTrace({ threadId: undefined });
if (args.path) {
return currentStack.stackFrames.some(frame => frame.source && frame.source.path === args.path);
}
else {
return currentStack.stackFrames.some(frame => frame.source && frame.source.sourceReference === args.sourceReference);
}
});
}
/* __GDPR__
"ClientRequest/loadedSources" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
loadedSources() {
return __awaiter(this, void 0, void 0, function* () {
const sources = yield Promise.all(Array.from(this._scriptContainer.loadedScripts)
.map(script => this._scriptContainer.scriptToSource(script, this.getReadonlyOrigin(script.url))));
return { sources: sources.sort((a, b) => a.path.localeCompare(b.path)) };
});
}
onConsoleAPICalled(event) {
if (this._launchAttachArgs._suppressConsoleOutput) {
return;
}
const result = consoleHelper_1.formatConsoleArguments(event.type, event.args, event.stackTrace);
const stack = internalSourceBreakpoint_1.stackTraceWithoutLogpointFrame(event.stackTrace);
if (result) {
this.logObjects(result.args, result.isError, stack);
}
}
onLogEntryAdded(event) {
// The Debug Console doesn't give the user a way to filter by level, just ignore 'verbose' logs
if (event.entry.level === 'verbose') {
return;
}
const args = event.entry.args || [];
let text = event.entry.text || '';
if (event.entry.url && !event.entry.stackTrace) {
if (text) {
text += ' ';
}
text += `[${event.entry.url}]`;
}
if (text) {
args.unshift({
type: 'string',
value: text
});
}
const type = event.entry.level === 'error' ? 'error' :
event.entry.level === 'warning' ? 'warning' :
'log';
const result = consoleHelper_1.formatConsoleArguments(type, args, event.entry.stackTrace);
const stack = event.entry.stackTrace;
if (result) {
this.logObjects(result.args, result.isError, stack);
}
}
logObjects(objs, isError = false, stackTrace) {
return __awaiter(this, void 0, void 0, function* () {
// This is an asynchronous method, so ensure that we handle one at a time so that they are sent out in the same order that they came in.
this._currentLogMessage = this._currentLogMessage
.then(() => __awaiter(this, void 0, void 0, function* () {
const category = isError ? 'stderr' : 'stdout';
// Shortcut the common log case to reduce unnecessary back and forth
let e;
if (objs.length === 1 && objs[0].type === 'string') {
let msg = objs[0].value;
if (isError) {
msg = yield this._stackFrames.mapFormattedException(msg, this._transformers);
}
if (!msg.endsWith(consoleHelper_1.clearConsoleCode)) {
// If this string will clear the console, don't append a \n
msg += '\n';
}
e = new vscode_debugadapter_1.OutputEvent(msg, category);
}
else {
e = new vscode_debugadapter_1.OutputEvent('output', category);
e.body.variablesReference = this._variablesManager.createHandle(new variables.LoggedObjects(objs), 'repl');
}
if (stackTrace && stackTrace.callFrames.length) {
const stackFrame = yield this._stackFrames.mapCallFrame(stackTrace.callFrames[0], this._transformers, this._scriptContainer, this.originProvider);
e.body.source = remoteMapper_1.mapInternalSourceToRemoteClient(stackFrame.source, this._launchAttachArgs.remoteAuthority);
e.body.line = stackFrame.line;
e.body.column = stackFrame.column;
}
this._session.sendEvent(e);
}))
.catch(err => vscode_debugadapter_1.logger.error(err.toString()));
});
}
onExceptionThrown(params) {
return __awaiter(this, void 0, void 0, function* () {
if (this._launchAttachArgs._suppressConsoleOutput) {
return;
}
return this._currentLogMessage = this._currentLogMessage.then(() => __awaiter(this, void 0, void 0, function* () {
const formattedException = consoleHelper_1.formatExceptionDetails(params.exceptionDetails);
const exceptionStr = yield this._stackFrames.mapFormattedException(formattedException, this._transformers);
const e = new vscode_debugadapter_1.OutputEvent(exceptionStr + '\n', 'stderr');
const stackTrace = params.exceptionDetails.stackTrace;
if (stackTrace && stackTrace.callFrames.length) {
const stackFrame = yield this._stackFrames.mapCallFrame(stackTrace.callFrames[0], this._transformers, this._scriptContainer, this.originProvider);
e.body.source = remoteMapper_1.mapInternalSourceToRemoteClient(stackFrame.source, this._launchAttachArgs.remoteAuthority);
e.body.line = stackFrame.line;
e.body.column = stackFrame.column;
}
this._session.sendEvent(e);
}))
.catch(err => vscode_debugadapter_1.logger.error(err.toString()));
});
}
/**
* For backcompat, also listen to Console.messageAdded, only if it looks like the old format.
*/
onMessageAdded(params) {
// message.type is undefined when Runtime.consoleAPICalled is being sent
if (params && params.message && params.message.type) {
const onConsoleAPICalledParams = {
type: params.message.type,
timestamp: params.message.timestamp,
args: params.message.parameters || [{ type: 'string', value: params.message.text }],
stackTrace: params.message.stack,
executionContextId: 1
};
this.onConsoleAPICalled(onConsoleAPICalledParams);
}
}
/* __GDPR__
"ClientRequest/disconnect" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
disconnect(args) {
telemetry_1.telemetry.reportEvent('FullSessionStatistics/SourceMaps/Overrides', { aspNetClientAppFallbackCount: sourceMapUtils.getAspNetFallbackCount() });
this._clientRequestedSessionEnd = true;
this.shutdown();
this.terminateSession('Got disconnect request', args);
}
/* __GDPR__
"ClientRequest/setBreakpoints" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
setBreakpoints(args, _, requestSeq, ids) {
if (args.source.path) {
args.source.path = remoteMapper_1.mapRemoteClientToInternalPath(args.source.path);
}
this.reportBpTelemetry(args);
return this._breakpoints.setBreakpoints(args, this._scriptContainer, requestSeq, ids);
}
reportBpTelemetry(args) {
let fileExt = '';
if (args.source.path) {
fileExt = path.extname(args.source.path);
fileExt = path.extname(args.source.path);
}
/* __GDPR__
"setBreakpointsRequest" : {
"fileExt" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('setBreakpointsRequest', { fileExt });
}
/* __GDPR__
"ClientRequest/setExceptionBreakpoints" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
setExceptionBreakpoints(args) {
let state;
if (args.filters.indexOf('all') >= 0) {
state = 'all';
}
else if (args.filters.indexOf('uncaught') >= 0) {
state = 'uncaught';
}
else {
state = 'none';
}
if (args.filters.indexOf('promise_reject') >= 0) {
this._pauseOnPromiseRejections = true;
}
else {
this._pauseOnPromiseRejections = false;
}
return this.chrome.Debugger.setPauseOnExceptions({ state })
.then(() => { });
}
/* __GDPR__
"ClientRequest/continue" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
/**
* internal -> suppress telemetry
*/
continue(internal = false) {
/* __GDPR__
"continueRequest" : {
"${include}": [ "${DebugCommonProperties}" ]
}
*/
if (!internal)
telemetry_1.telemetry.reportEvent('continueRequest');
if (!this.chrome) {
return utils.errP(errors.runtimeNotConnectedMsg);
}
this._expectingResumedEvent = true;
return this._currentStep = this.chrome.Debugger.resume()
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/next" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
next() {
if (!this.chrome) {
return utils.errP(errors.runtimeNotConnectedMsg);
}
/* __GDPR__
"nextRequest" : {
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('nextRequest');
this._expectingStopReason = 'step';
this._expectingResumedEvent = true;
return this._currentStep = this.chrome.Debugger.stepOver()
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/stepIn" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
stepIn(userInitiated = true) {
if (!this.chrome) {
return utils.errP(errors.runtimeNotConnectedMsg);
}
if (userInitiated) {
/* __GDPR__
"stepInRequest" : {
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('stepInRequest');
}
this._expectingStopReason = 'step';
this._expectingResumedEvent = true;
return this._currentStep = this.chrome.Debugger.stepInto({ breakOnAsyncCall: true })
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/stepOut" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
stepOut() {
if (!this.chrome) {
return utils.errP(errors.runtimeNotConnectedMsg);
}
/* __GDPR__
"stepOutRequest" : {
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('stepOutRequest');
this._expectingStopReason = 'step';
this._expectingResumedEvent = true;
return this._currentStep = this.chrome.Debugger.stepOut()
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/stepBack" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
stepBack() {
return this.chrome.TimeTravel.stepBack()
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/reverseContinue" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
reverseContinue() {
return this.chrome.TimeTravel.reverse()
.then(() => { }, () => { });
}
/* __GDPR__
"ClientRequest/pause" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
pause() {
if (!this.chrome) {
return utils.errP(errors.runtimeNotConnectedMsg);
}
/* __GDPR__
"pauseRequest" : {
"${include}": [ "${DebugCommonProperties}" ]
}
*/
telemetry_1.telemetry.reportEvent('pauseRequest');
this._expectingStopReason = 'pause';
return this._currentStep = this.chrome.Debugger.pause()
.then(() => { });
}
/* __GDPR__
"ClientRequest/stackTrace" : {
"${include}": [
"${IExecutionResultTelemetryProperties}",
"${DebugCommonProperties}"
]
}
*/
stackTrace(args) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._currentPauseNotification) {
return Promise.reject(errors.noCallStackAvailable());
}
const stackTraceResponse = yield this._stackFrames.getStack