import { DebugProtocol } from 'vscode-debugprotocol' ; import { InitializedEvent , TerminatedEvent , ContinuedEvent , OutputEvent , Logger , logger , LoadedSourceEvent } from 'vscode-debugadapter' ; import { ICommonRequestArgs , ILaunchRequestArgs , ISetBreakpointsArgs , ISetBreakpointsResponseBody , IStackTraceResponseBody , IAttachRequestArgs , IScopesResponseBody , IVariablesResponseBody , ISourceResponseBody , IThreadsResponseBody , IEvaluateResponseBody , IDebugAdapter , ICompletionsResponseBody , IToggleSkipFileStatusArgs , ISetBreakpointResult , IRestartRequestArgs , IInitializeRequestArgs , ITelemetryPropertyCollector , IGetLoadedSourcesResponseBody , TimeTravelRuntime , IExceptionInfoResponseBody , ISetVariableResponseBody } from '../debugAdapterInterfaces' ; import { IChromeDebugAdapterOpts , ChromeDebugSession } from './chromeDebugSession' ; import { ChromeConnection } from './chromeConnection' ; import * as ChromeUtils from './chromeUtils' ; import { Protocol as Crdp } from 'devtools-protocol' ; import { ScopeContainer , isIndexedPropName } from './variables' ; import * as variables from './variables' ; import { formatConsoleArguments , formatExceptionDetails , clearConsoleCode } from './consoleHelper' ; import { StoppedEvent2 , ReasonType } from './stoppedEvent' ; import { stackTraceWithoutLogpointFrame } from './internalSourceBreakpoint' ; import * as errors from '../errors' ; import * as utils from '../utils' ; import { PromiseDefer , promiseDefer } from '../utils' ; import { telemetry , BatchTelemetryReporter , IExecutionResultTelemetryProperties } from '../telemetry' ; import { StepProgressEventsEmitter } from '../executionTimingsReporter' ; import { LineColTransformer } from '../transformers/lineNumberTransformer' ; import { BasePathTransformer } from '../transformers/basePathTransformer' ; import { RemotePathTransformer } from '../transformers/remotePathTransformer' ; import { BaseSourceMapTransformer } from '../transformers/baseSourceMapTransformer' ; import { EagerSourceMapTransformer } from '../transformers/eagerSourceMapTransformer' ; import { FallbackToClientPathTransformer } from '../transformers/fallbackToClientPathTransformer' ; import { BreakOnLoadHelper } from './breakOnLoadHelper' ; import * as sourceMapUtils from '../sourceMaps/sourceMapUtils' ; import * as path from 'path' ; import * as nls from 'vscode-nls' ; import { mapRemoteClientToInternalPath , mapInternalSourceToRemoteClient } from '../remoteMapper' ; import { Breakpoints } from './breakpoints' ; import { VariablesManager } from './variablesManager' ; import { StackFrames } from './stackFrames' ; import { ScriptContainer } from './scripts' ; import { SmartStepper } from './smartStep' ; import { ScriptSkipper } from './scriptSkipping' ; let localize = nls . loadMessageBundle ( ) ; export interface IPendingBreakpoint { args : ISetBreakpointsArgs ; ids : number [ ] ; requestSeq : number ; setWithPath : string ; } export type VariableContext = 'variables' | 'watch' | 'repl' | 'hover' ; export type CrdpScript = Crdp . Debugger . ScriptParsedEvent ; export type CrdpDomain = string ; export type LoadedSourceEventReason = 'new' | 'changed' | 'removed' ; export interface BreakpointSetResult { isSet : boolean ; breakpoint : DebugProtocol . Breakpoint ; } export interface IOnPausedResult { didPause : boolean ; } export interface Transformers { lineColTransformer : LineColTransformer ; sourceMapTransformer : BaseSourceMapTransformer ; pathTransformer : BasePathTransformer ; } export abstract class ChromeDebugAdapter implements IDebugAdapter { public static EVAL_NAME_PREFIX = ChromeUtils . EVAL_NAME_PREFIX ; public static EVAL_ROOT = '<eval>' ; /**
     * Names of variables and properties to be filtered out of the results
     * from the adapter.
     */ private static FILTERED_VARIABLE_NAMES = [ '[[StableObjectId]]' ] ; private static SCRIPTS_COMMAND = '.scripts' ; private static THREAD_ID = 1 ; private static ASYNC_CALL_STACK_DEPTH = 4 ; protected _session : ChromeDebugSession ; protected _domains = new Map < CrdpDomain , Crdp . Schema . Domain > ( ) ; private _clientAttached : boolean ; private _currentPauseNotification : Crdp . Debugger . PausedEvent ; private _exception : Crdp . Runtime . RemoteObject ; private _expectingResumedEvent : boolean ; protected _expectingStopReason : ReasonType ; private _waitAfterStep = Promise . resolve ( ) ; protected _chromeConnection : ChromeConnection ; protected _clientRequestedSessionEnd : boolean ; protected _hasTerminated : boolean ; protected _inShutdown : boolean ; protected _attachMode : boolean ; protected _launchAttachArgs : ICommonRequestArgs ; protected _port : number ; private _currentStep = Promise . resolve ( ) ; private _currentLogMessage = Promise . resolve ( ) ; private _pauseOnPromiseRejections = true ; protected _promiseRejectExceptionFilterEnabled = false ; private _columnBreakpointsEnabled : boolean ; private _smartStepEnabled : boolean ; private _smartStepCount = 0 ; private _earlyScripts : Crdp . Debugger . ScriptParsedEvent [ ] = [ ] ; private _initialSourceMapsP = Promise . resolve ( ) ; private _lastPauseState : { expecting : ReasonType ; event : Crdp . Debugger . PausedEvent ; } ; protected _breakOnLoadHelper : BreakOnLoadHelper | null ; private _sourceLoadedQueue : Promise < void > = Promise . resolve ( null ) ; private _scriptIdToBreakpointsAreResolvedDefer = new Map < string , PromiseDefer < void > > ( ) ; private _batchTelemetryReporter : BatchTelemetryReporter ; public readonly events : StepProgressEventsEmitter ; private _loadedSourcesByScriptId = new Map < Crdp . Runtime . ScriptId , CrdpScript > ( ) ; protected _isVSClient : boolean ; public get columnBreakpointsEnabled ( ) { return this . _columnBreakpointsEnabled ; } public get breakOnLoadHelper ( ) { return this . _breakOnLoadHelper ; } protected _scriptContainer : ScriptContainer ; protected _breakpoints : Breakpoints ; protected _variablesManager : VariablesManager ; protected _stackFrames : StackFrames ; protected _smartStepper : SmartStepper ; protected _scriptSkipper : ScriptSkipper ; private _transformers : Transformers ; public constructor ( { chromeConnection , lineColTransformer , sourceMapTransformer , pathTransformer , targetFilter , breakpoints , scriptContainer } , session ) { telemetry . setupEventHandler ( e => session . sendEvent ( e ) ) ; this . _batchTelemetryReporter = new BatchTelemetryReporter ( telemetry ) ; this . _session = session ; this . _chromeConnection = new ( chromeConnection || ChromeConnection ) ( undefined , targetFilter ) ; this . events = new StepProgressEventsEmitter ( this . _chromeConnection . events ? [ this . _chromeConnection . events ] : [ ] ) ; this . _scriptContainer = new ( scriptContainer || ScriptContainer ) ( ) ; this . _transformers = { lineColTransformer : new ( lineColTransformer || LineColTransformer ) ( this . _session ) , sourceMapTransformer : new ( sourceMapTransformer || EagerSourceMapTransformer ) ( this . _scriptContainer ) , pathTransformer : new ( pathTransformer || RemotePathTransformer ) ( ) } ; this . _breakpoints = new ( breakpoints || Breakpoints ) ( this , this . _chromeConnection ) ; this . _variablesManager = new VariablesManager ( this . _chromeConnection ) ; this . _stackFrames = new StackFrames ( ) ; this . _scriptSkipper = new ScriptSkipper ( this . _chromeConnection , this . _transformers ) ; this . clearTargetContext ( ) ; } public get chrome ( ) : Crdp . ProtocolApi { return this . _chromeConnection . api ; } deprecated public get scriptsById ( ) : Map < Crdp . Runtime . ScriptId , CrdpScript > { return this . _scriptContainer . scriptsByIdMap ; } public get committedBreakpointsByUrl ( ) : Map < string , ISetBreakpointResult [ ] > { return this . _breakpoints . committedBreakpointsByUrl ; } public get pathTransformer ( ) : BasePathTransformer { return this . _transformers . pathTransformer ; } public get sourceMapTransformer ( ) : BaseSourceMapTransformer { return this . _transformers . sourceMapTransformer ; } public get lineColTransformer ( ) : LineColTransformer { return this . _transformers . lineColTransformer ; } public get session ( ) { return this . _session ; } private get originProvider ( ) { return ( url ) => this . getReadonlyOrigin ( url ) ; } /**
     * Called on 'clearEverything' or on a navigation/refresh
     */ protected clearTargetContext ( ) { this . sourceMapTransformer . clearTargetContext ( ) ; this . _scriptContainer . reset ( ) ; if ( this . _breakpoints ) { this . _breakpoints . reset ( ) ; } this . pathTransformer . clearTargetContext ( ) ; } public initialize ( args ) { if ( args . supportsMapURLToFilePathRequest ) { this . _transformers . pathTransformer = new 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 } ) ( ) ; } if ( typeof args . linesStartAt1 === 'boolean' ) { ( < any > this ) . _clientLinesStartAt1 = args . linesStartAt1 ; } if ( typeof args . columnsStartAt1 === 'boolean' ) { ( < any > this ) . _clientColumnsStartAt1 = args . columnsStartAt1 ; } const exceptionBreakpointFilters = [ { label : localize ( 'exceptions.all' , 'All Exceptions' ) , filter : 'all' , default : false } , { label : localize ( 'exceptions.uncaught' , 'Uncaught Exceptions' ) , filter : 'uncaught' , default : false } ] ; if ( this . _promiseRejectExceptionFilterEnabled ) { exceptionBreakpointFilters . push ( { label : localize ( 'exceptions.promise_rejects' , 'Promise Rejects' ) , filter : 'promise_reject' , default : false } ) ; } 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 } ; } public configurationDone ( ) { return Promise . resolve ( ) ; } public get breakOnLoadActive ( ) : boolean { return ! ! this . _breakOnLoadHelper ; } public async launch ( args , telemetryPropertyCollector ? ) { this . commonArgs ( args ) ; if ( args . pathMapping ) { for ( const urlToMap in args . pathMapping ) { args . pathMapping [ urlToMap ] = utils . canonicalizeUrl ( args . pathMapping [ urlToMap ] ) ; } } this . sourceMapTransformer . launch ( args ) ; await this . pathTransformer . launch ( args ) ; if ( ! args . __restart ) { telemetry . reportEvent ( 'debugStarted' , { request : 'launch' , args : Object . keys ( args ) } ) ; } } public async attach ( args ) { this . _attachMode = true ; this . commonArgs ( args ) ; this . sourceMapTransformer . attach ( args ) ; await this . pathTransformer . attach ( args ) ; if ( ! args . port ) { args . port = 9229 ; } telemetry . reportEvent ( 'debugStarted' , { request : 'attach' , args : Object . keys ( args ) } ) ; await this . doAttach ( args . port , args . url , args . address , args . timeout , args . websocketUrl , args . extraCRDPChannelPort ) ; } protected commonArgs ( args ) { let logToFile = false ; let logLevel ; if ( args . trace === 'verbose' ) { logLevel = Logger . LogLevel . Verbose ; logToFile = true ; } else if ( args . trace ) { logLevel = Logger . LogLevel . Warn ; logToFile = true ; } else { logLevel = Logger . LogLevel . Warn ; } let logTimestamps = args . logTimestamps ; const logFilePath = args . logFilePath || logToFile ; logger . setup ( logLevel , logFilePath , logTimestamps ) ; this . _launchAttachArgs = args ; args . sourceMaps = typeof args . sourceMaps === 'undefined' || args . sourceMaps ; args . showAsyncStacks = typeof args . showAsyncStacks === 'undefined' || args . showAsyncStacks ; this . _smartStepper = new SmartStepper ( this . _launchAttachArgs . smartStep ) ; if ( args . breakOnLoadStrategy && args . breakOnLoadStrategy !== 'off' ) { this . _breakOnLoadHelper = new BreakOnLoadHelper ( this , args . breakOnLoadStrategy ) ; } if ( args . hasOwnProperty ( 'targetFilter' ) ) { this . _chromeConnection . setTargetFilter ( args . targetFilter ) ; } } public shutdown ( ) { this . _batchTelemetryReporter . finalize ( ) ; this . _inShutdown = true ; this . _session . shutdown ( ) ; } protected async terminateSession ( reason , _disconnectArgs ? , restart ? ) { logger . log ( `Terminated: ${ reason }` ) ; if ( ! this . _hasTerminated ) { logger . log ( `Waiting for any pending steps or log messages.` ) ; await this . _currentStep ; await this . _currentLogMessage ; logger . log ( `Current step and log messages complete` ) ; telemetry . reportEvent ( 'debugStopped' , { reason } ) ; this . _hasTerminated = true ; if ( this . _clientAttached || ( this . _launchAttachArgs && ( < ILaunchRequestArgs > this . _launchAttachArgs ) . noDebug ) ) { this . _session . sendEvent ( new TerminatedEvent ( restart ) ) ; } if ( this . _chromeConnection . isAttached ) { this . _chromeConnection . close ( ) ; } } } /**
     * Hook up all connection events
     */ protected hookConnectionEvents ( ) { this . chrome . Debugger . on ( 'paused' , params => { this . runAndMeasureProcessingTime ( 'target/notification/onPaused' , async ( ) => { await this . onPaused ( params ) ; } ) ; } ) ; this . chrome . Debugger . on ( 'resumed' , ( ) => this . onResumed ( ) ) ; this . chrome . Debugger . on ( 'scriptParsed' , params => { 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' ) ) ; } private async runAndMeasureProcessingTime ( notificationName , procedure ) { const startTime = Date . now ( ) ; const startTimeMark = process . hrtime ( ) ; let properties = { startTime : startTime . toString ( ) } ; try { await 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 ( ) ; this . _batchTelemetryReporter . reportEvent ( notificationName , properties ) ; } /**
     * Enable clients and run connection
     */ protected 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 ( ) , ] ; } protected async doAttach ( port , targetUrl ? , address ? , timeout ? , websocketUrl ? , extraCRDPChannelPort ? ) { this . events . emitStepStarted ( 'Attach' ) ; this . _clientAttached = true ; if ( ! this . _chromeConnection . isAttached ) { if ( websocketUrl ) { await this . _chromeConnection . attachToWebsocketUrl ( websocketUrl , extraCRDPChannelPort ) ; } else { await this . _chromeConnection . attach ( address , port , targetUrl , timeout , extraCRDPChannelPort ) ; } this . events . emitStepStarted ( 'Attach.ConfigureDebuggingSession.Internal' ) ; this . _port = port ; this . hookConnectionEvents ( ) ; this . events . emitStepStarted ( 'Attach.ConfigureDebuggingSession.Target' ) ; await Promise . all ( this . runConnection ( ) ) ; this . _scriptSkipper . init ( this . _launchAttachArgs . skipFiles , this . _launchAttachArgs . skipFileRegExps ) ; await this . initSupportedDomains ( ) ; const maxDepth = this . _launchAttachArgs . showAsyncStacks ? ChromeDebugAdapter . ASYNC_CALL_STACK_DEPTH : 0 ; try { await this . chrome . Debugger . setAsyncCallStackDepth ( { maxDepth } ) ; } catch ( e ) { } if ( this . _breakOnLoadHelper ) { this . _breakOnLoadHelper . setBrowserVersion ( ( await this . _chromeConnection . version ) . browser ) ; } this . events . emitStepStarted ( 'Attach.ConfigureDebuggingSession.End' ) ; } } private async initSupportedDomains ( ) { try { const domainResponse = await this . chrome . Schema . getDomains ( ) ; domainResponse . domains . forEach ( domain => this . _domains . set ( < any > domain . name , domain ) ) ; } catch ( e ) { } } /**
     * 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.
     */ protected async sendInitializedEvent ( ) { if ( this . _initialSourceMapsP ) { const initialSourceMapsP = this . _initialSourceMapsP ; this . _initialSourceMapsP = null ; await initialSourceMapsP ; this . _session . sendEvent ( new InitializedEvent ( ) ) ; this . events . emitStepCompleted ( 'NotifyInitialized' ) ; await Promise . all ( this . _earlyScripts . map ( script => this . sendLoadedSourceEvent ( script ) ) ) ; this . _earlyScripts = null ; } } public doAfterProcessingSourceEvents ( action ) { return this . _sourceLoadedQueue = this . _sourceLoadedQueue . then ( action ) ; } /**
     * e.g. the target navigated
     */ protected onExecutionContextsCleared ( ) { const cachedScriptParsedEvents = Array . from ( this . _scriptContainer . loadedScripts ) ; this . clearTargetContext ( ) ; return this . doAfterProcessingSourceEvents ( async ( ) => { for ( let scriptedParseEvent of cachedScriptParsedEvents ) { this . sendLoadedSourceEvent ( scriptedParseEvent , 'removed' ) ; } } ) ; } protected async onPaused ( notification , expectingStopReason = this . _expectingStopReason ) { if ( notification . asyncCallStackTraceId ) { await this . chrome . Debugger . pauseOnAsyncCall ( { parentStackTraceId : notification . asyncCallStackTraceId } ) ; await 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 ( this . breakOnLoadActive ) { let shouldContinue = await this . _breakOnLoadHelper . handleOnPaused ( notification ) ; if ( shouldContinue ) { this . chrome . Debugger . resume ( ) . catch ( e => { logger . error ( 'Failed to resume due to exception: ' + e . message ) ; } ) ; return { didPause : false } ; } } let reason ; let shouldSmartStep = false ; if ( notification . reason === 'exception' ) { reason = 'exception' ; this . _exception = notification . data ; } else if ( notification . reason === 'promiseRejection' ) { reason = 'promise_rejection' ; 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 ) { reason = expectingStopReason ; shouldSmartStep = await this . _shouldSmartStepCallFrame ( this . _currentPauseNotification . callFrames [ 0 ] ) ; } else { reason = 'debugger_statement' ; } this . _expectingStopReason = undefined ; if ( shouldSmartStep ) { this . _smartStepCount ++ ; await this . stepIn ( false ) ; return { didPause : false } ; } else { if ( this . _smartStepCount > 0 ) { logger . log ( `SmartStep: Skipped ${ this . _smartStepCount } steps` ) ; this . _smartStepCount = 0 ; } const sendStoppedEvent = ( ) => { return this . _session . sendEvent ( new StoppedEvent2 ( reason , ChromeDebugAdapter . THREAD_ID , this . _exception ) ) ; } ; await utils . promiseTimeout ( this . _currentStep , 300 ) . then ( sendStoppedEvent , sendStoppedEvent ) ; return { didPause : true } ; } } public async exceptionInfo ( args ) { 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 && await this . _stackFrames . mapFormattedException ( this . _exception . description , this . _transformers ) , message , formattedDescription : formattedMessage , typeName : this . _exception . subtype || this . _exception . type } } ; return response ; } else { throw errors . noStoredException ( ) ; } } protected onResumed ( ) { this . _currentPauseNotification = null ; if ( this . _expectingResumedEvent ) { this . _expectingResumedEvent = false ; this . _waitAfterStep = utils . promiseTimeout ( null , 50 ) ; } else { let resumedEvent = new ContinuedEvent ( ChromeDebugAdapter . THREAD_ID ) ; this . _session . sendEvent ( resumedEvent ) ; } } private async detectColumnBreakpointSupport ( scriptId ) { this . _columnBreakpointsEnabled = false ; try { await 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 ; } public getBreakpointsResolvedDefer ( scriptId ) { const existingValue = this . _scriptIdToBreakpointsAreResolvedDefer . get ( scriptId ) ; if ( existingValue ) { return existingValue ; } else { const newValue = promiseDefer < void > ( ) ; this . _scriptIdToBreakpointsAreResolvedDefer . set ( scriptId , newValue ) ; return newValue ; } } protected async onScriptParsed ( script ) { delete script . stackTrace ; delete script . hash ; const breakpointsAreResolvedDefer = this . getBreakpointsResolvedDefer ( script . scriptId ) ; try { this . doAfterProcessingSourceEvents ( async ( ) => { if ( typeof this . _columnBreakpointsEnabled === 'undefined' ) { if ( ! script . url . includes ( 'internal/per_context' ) ) { await this . detectColumnBreakpointSupport ( script . scriptId ) ; await this . sendInitializedEvent ( ) ; } } if ( this . _earlyScripts ) { this . _earlyScripts . push ( script ) ; } else { await 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 = await this . pathTransformer . scriptParsed ( script . url ) ; const sourceMapsP = this . sourceMapTransformer . scriptParsed ( mappedUrl , script . url , script . sourceMapURL ) . then ( async ( sources ) => { if ( this . _hasTerminated ) { return undefined ; } await this . _breakpoints . handleScriptParsed ( script , this . _scriptContainer , mappedUrl , sources ) ; await this . _scriptSkipper . resolveSkipFiles ( script , mappedUrl , sources ) ; } ) ; if ( this . _initialSourceMapsP ) { this . _initialSourceMapsP = < Promise < any > > Promise . all ( [ this . _initialSourceMapsP , sourceMapsP ] ) ; } await sourceMapsP ; breakpointsAreResolvedDefer . resolve ( ) ; } catch ( exception ) { breakpointsAreResolvedDefer . reject ( exception ) ; } } protected async sendLoadedSourceEvent ( script , loadedSourceEventReason = 'new' ) { const origin = this . getReadonlyOrigin ( script . url ) ; const source = await this . _scriptContainer . scriptToSource ( script , origin ) ; switch ( loadedSourceEventReason ) { case 'new' : case 'changed' : if ( this . _loadedSourcesByScriptId . get ( script . scriptId ) ) { if ( source . sourceReference ) { loadedSourceEventReason = 'changed' ; } else { return ; } } else { loadedSourceEventReason = 'new' ; } this . _loadedSourcesByScriptId . set ( script . scriptId , script ) ; break ; case 'removed' : if ( ! this . _loadedSourcesByScriptId . delete ( script . scriptId ) ) { telemetry . reportEvent ( 'LoadedSourceEventError' , { issue : 'Tried to remove non-existent script' , scriptId : script . scriptId } ) ; return ; } break ; default : telemetry . reportEvent ( 'LoadedSourceEventError' , { issue : 'Unknown reason' , reason : loadedSourceEventReason } ) ; } const scriptEvent = new LoadedSourceEvent ( loadedSourceEventReason , source as any ) ; this . _session . sendEvent ( scriptEvent ) ; } public async toggleSmartStep ( ) { this . _smartStepEnabled = ! this . _smartStepEnabled ; this . onPaused ( this . _lastPauseState . event , this . _lastPauseState . expecting ) ; } public async toggleSkipFileStatus ( args ) { if ( args . path ) { args . path = utils . fileUrlToPath ( args . path ) ; args . path = mapRemoteClientToInternalPath ( args . path ) ; } if ( ! await this . isInCurrentStack ( args ) ) { const logName = args . path || this . _scriptContainer . displayNameForSourceReference ( args . sourceReference ) ; 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 ) ; } } private async isInCurrentStack ( args ) { const currentStack = await 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 ) ; } } public async loadedSources ( ) { const sources = await 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 ) ) } ; } protected onConsoleAPICalled ( event ) { if ( this . _launchAttachArgs . _suppressConsoleOutput ) { return ; } const result = formatConsoleArguments ( event . type , event . args , event . stackTrace ) ; const stack = stackTraceWithoutLogpointFrame ( event . stackTrace ) ; if ( result ) { this . logObjects ( result . args , result . isError , stack ) ; } } private onLogEntryAdded ( event ) { 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 = formatConsoleArguments ( type , args , event . entry . stackTrace ) ; const stack = event . entry . stackTrace ; if ( result ) { this . logObjects ( result . args , result . isError , stack ) ; } } private async logObjects ( objs , isError = false , stackTrace ? ) { this . _currentLogMessage = this . _currentLogMessage . then ( async ( ) => { const category = isError ? 'stderr' : 'stdout' ; let e ; if ( objs . length === 1 && objs [ 0 ] . type === 'string' ) { let msg = objs [ 0 ] . value ; if ( isError ) { msg = await this . _stackFrames . mapFormattedException ( msg , this . _transformers ) ; } if ( ! msg . endsWith ( clearConsoleCode ) ) { msg += '\n' ; } e = new OutputEvent ( msg , category ) ; } else { e = new OutputEvent ( 'output' , category ) ; e . body . variablesReference = this . _variablesManager . createHandle ( new variables . LoggedObjects ( objs ) , 'repl' ) ; } if ( stackTrace && stackTrace . callFrames . length ) { const stackFrame = await this . _stackFrames . mapCallFrame ( stackTrace . callFrames [ 0 ] , this . _transformers , this . _scriptContainer , this . originProvider ) ; e . body . source = mapInternalSourceToRemoteClient ( stackFrame . source , this . _launchAttachArgs . remoteAuthority ) ; e . body . line = stackFrame . line ; e . body . column = stackFrame . column ; } this . _session . sendEvent ( e ) ; } ) . catch ( err => logger . error ( err . toString ( ) ) ) ; } protected async onExceptionThrown ( params ) { if ( this . _launchAttachArgs . _suppressConsoleOutput ) { return ; } return this . _currentLogMessage = this . _currentLogMessage . then ( async ( ) => { const formattedException = formatExceptionDetails ( params . exceptionDetails ) ; const exceptionStr = await this . _stackFrames . mapFormattedException ( formattedException , this . _transformers ) ; const e = new OutputEvent ( exceptionStr + '\n' , 'stderr' ) ; const stackTrace = params . exceptionDetails . stackTrace ; if ( stackTrace && stackTrace . callFrames . length ) { const stackFrame = await this . _stackFrames . mapCallFrame ( stackTrace . callFrames [ 0 ] , this . _transformers , this . _scriptContainer , this . originProvider ) ; e . body . source = mapInternalSourceToRemoteClient ( stackFrame . source , this . _launchAttachArgs . remoteAuthority ) ; e . body . line = stackFrame . line ; e . body . column = stackFrame . column ; } this . _session . sendEvent ( e ) ; } ) . catch ( err => logger . error ( err . toString ( ) ) ) ; } /**
     * For backcompat, also listen to Console.messageAdded, only if it looks like the old format.
     */ protected onMessageAdded ( params ) { 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 ) ; } } public disconnect ( args ) { telemetry . reportEvent ( 'FullSessionStatistics/SourceMaps/Overrides' , { aspNetClientAppFallbackCount : sourceMapUtils . getAspNetFallbackCount ( ) } ) ; this . _clientRequestedSessionEnd = true ; this . shutdown ( ) ; this . terminateSession ( 'Got disconnect request' , args ) ; } public setBreakpoints ( args , _ , requestSeq , ids ? ) { if ( args . source . path ) { args . source . path = mapRemoteClientToInternalPath ( args . source . path ) ; } this . reportBpTelemetry ( args ) ; return this . _breakpoints . setBreakpoints ( args , this . _scriptContainer , requestSeq , ids ) ; } private reportBpTelemetry ( args ) { let fileExt = '' ; if ( args . source . path ) { fileExt = path . extname ( args . source . path ) ; fileExt = path . extname ( args . source . path ) ; } telemetry . reportEvent ( 'setBreakpointsRequest' , { fileExt } ) ; } public 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 ( ( ) => { } ) ; } /**
     * internal -> suppress telemetry
     */ public continue ( internal = false ) { if ( ! internal ) telemetry . reportEvent ( 'continueRequest' ) ; if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } this . _expectingResumedEvent = true ; return this . _currentStep = this . chrome . Debugger . resume ( ) . then ( ( ) => { } , ( ) => { } ) ; } public next ( ) { if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } telemetry . reportEvent ( 'nextRequest' ) ; this . _expectingStopReason = 'step' ; this . _expectingResumedEvent = true ; return this . _currentStep = this . chrome . Debugger . stepOver ( ) . then ( ( ) => { } , ( ) => { } ) ; } public stepIn ( userInitiated = true ) { if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } if ( userInitiated ) { telemetry . reportEvent ( 'stepInRequest' ) ; } this . _expectingStopReason = 'step' ; this . _expectingResumedEvent = true ; return this . _currentStep = this . chrome . Debugger . stepInto ( { breakOnAsyncCall : true } ) . then ( ( ) => { } , ( ) => { } ) ; } public stepOut ( ) { if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } telemetry . reportEvent ( 'stepOutRequest' ) ; this . _expectingStopReason = 'step' ; this . _expectingResumedEvent = true ; return this . _currentStep = this . chrome . Debugger . stepOut ( ) . then ( ( ) => { } , ( ) => { } ) ; } public stepBack ( ) { return ( < TimeTravelRuntime > this . chrome ) . TimeTravel . stepBack ( ) . then ( ( ) => { } , ( ) => { } ) ; } public reverseContinue ( ) { return ( < TimeTravelRuntime > this . chrome ) . TimeTravel . reverse ( ) . then ( ( ) => { } , ( ) => { } ) ; } public pause ( ) { if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } telemetry . reportEvent ( 'pauseRequest' ) ; this . _expectingStopReason = 'pause' ; return this . _currentStep = this . chrome . Debugger . pause ( ) . then ( ( ) => { } ) ; } public async stackTrace ( args ) { if ( ! this . _currentPauseNotification ) { return Promise . reject ( errors . noCallStackAvailable ( ) ) ; } const stackTraceResponse = await this . _stackFrames . getStackTrace ( { args , originProvider : this . originProvider , scripts : this . _scriptContainer , scriptSkipper : this . _scriptSkipper , smartStepper : this . _smartStepper , transformers : this . _transformers , pauseEvent : this . _currentPauseNotification } ) ; stackTraceResponse . stackFrames = stackTraceResponse . stackFrames . map ( frame => { return { ... frame , source : mapInternalSourceToRemoteClient ( frame . source , this . _launchAttachArgs . remoteAuthority ) } ; } ) ; return stackTraceResponse ; } /**
     * A stub method for overriding (used for the node debug adapter)
     */ protected getReadonlyOrigin ( _url ) { return undefined ; } public realPathToDisplayPath ( realPath ) { return this . _scriptContainer . realPathToDisplayPath ( realPath ) ; } public displayPathToRealPath ( displayPath ) { return this . _scriptContainer . displayPathToRealPath ( displayPath ) ; } public scopes ( args ) { return this . _stackFrames . getScopes ( { args , scripts : this . _scriptContainer , variables : this . _variablesManager , transformers : this . _transformers , pauseEvent : this . _currentPauseNotification , currentException : this . _exception } ) ; } public async variables ( args ) { const result = await this . _variablesManager . getVariables ( args ) ; const variables = result ? result . variables : [ ] ; return { variables : variables . filter ( v => ChromeDebugAdapter . FILTERED_VARIABLE_NAMES . indexOf ( v . name ) === - 1 ) } ; } public source ( args ) { let scriptId ; if ( args . sourceReference ) { const handle = this . _scriptContainer . getSource ( args . sourceReference ) ; if ( ! handle ) { return Promise . reject ( errors . sourceRequestIllegalHandle ( ) ) ; } if ( handle . contents ) { return Promise . resolve ( { content : handle . contents } ) ; } scriptId = handle . scriptId ; } else if ( args . source && args . source . path ) { const realPath = this . displayPathToRealPath ( args . source . path ) ; const script = this . getScriptByUrl ( utils . isURL ( realPath ) ? encodeURI ( realPath ) : realPath ) ; if ( ! script ) { return Promise . reject ( errors . sourceRequestCouldNotRetrieveContent ( ) ) ; } scriptId = script . scriptId ; } if ( ! scriptId ) { return Promise . reject ( errors . sourceRequestCouldNotRetrieveContent ( ) ) ; } return this . chrome . Debugger . getScriptSource ( { scriptId } ) . then ( response => { return { content : response . scriptSource , mimeType : 'text/javascript' } ; } ) ; } public threads ( ) { return { threads : [ { id : ChromeDebugAdapter . THREAD_ID , name : this . threadName ( ) } ] } ; } protected threadName ( ) { return 'Thread ' + ChromeDebugAdapter . THREAD_ID ; } public async evaluate ( args ) { if ( ! this . chrome ) { return utils . errP ( errors . runtimeNotConnectedMsg ) ; } if ( args . expression . startsWith ( ChromeDebugAdapter . SCRIPTS_COMMAND ) ) { return this . handleScriptsCommand ( args ) ; } if ( args . expression . startsWith ( '{' ) && args . expression . endsWith ( '}' ) ) { args . expression = `(${ args . expression })` ; } const evalResponse = await this . waitThenDoEvaluate ( args . expression , args . frameId , { generatePreview : true } ) ; const variable = await this . _variablesManager . remoteObjectToVariable ( args . expression , evalResponse . result , undefined , undefined , < VariableContext > args . context ) ; if ( evalResponse . exceptionDetails ) { let resultValue = variable . value ; if ( resultValue && ( resultValue . startsWith ( 'ReferenceError: ' ) || resultValue . startsWith ( 'TypeError: ' ) ) && args . context !== 'repl' ) { resultValue = errors . evalNotAvailableMsg ; } return utils . errP ( resultValue ) ; } return < IEvaluateResponseBody > { result : variable . value , variablesReference : variable . variablesReference , indexedVariables : variable . indexedVariables , namedVariables : variable . namedVariables , type : variable . type } ; } /**
     * Handle the .scripts command, which can be used as `.scripts` to return a list of all script details,
     * or `.scripts <url>` to show the contents of the given script.
     */ private handleScriptsCommand ( args ) { let outputStringP ; const scriptsRest = utils . lstrip ( args . expression , ChromeDebugAdapter . SCRIPTS_COMMAND ) . trim ( ) ; if ( scriptsRest ) { const requestedScript = this . getScriptByUrl ( scriptsRest ) ; if ( requestedScript ) { outputStringP = this . chrome . Debugger . getScriptSource ( { scriptId : requestedScript . scriptId } ) . then ( result => { const maxLength = 1e5 ; return result . scriptSource . length > maxLength ? result . scriptSource . substr ( 0 , maxLength ) + '[⋯]' : result . scriptSource ; } ) ; } else { outputStringP = Promise . resolve ( `No runtime script with url: ${ scriptsRest }\n` ) ; } } else { outputStringP = this . _scriptContainer . getAllScriptsString ( this . pathTransformer , this . sourceMapTransformer ) ; } return outputStringP . then ( scriptsStr => { this . _session . sendEvent ( new OutputEvent ( scriptsStr ) ) ; return < IEvaluateResponseBody > { result : '' , variablesReference : 0 } ; } ) ; } private async _shouldSmartStepCallFrame ( frame ) { const stackFrame = this . _stackFrames . callFrameToStackFrame ( frame , this . _scriptContainer , this . originProvider ) ; const fakeResponse = { stackFrames : [ stackFrame ] } ; await this . pathTransformer . stackTraceResponse ( fakeResponse ) ; await this . sourceMapTransformer . stackTraceResponse ( fakeResponse ) ; return this . _smartStepper . shouldSmartStep ( fakeResponse . stackFrames [ 0 ] , this . pathTransformer , this . sourceMapTransformer ) ; } /**
     * Allow consumers to override just because of https://github.com/nodejs/node/issues/8426
     */ protected globalEvaluate ( args ) { return this . chrome . Runtime . evaluate ( args ) ; } private async waitThenDoEvaluate ( expression , frameId ? , extraArgs ? ) { const waitThenEval = this . _waitAfterStep . then ( ( ) => this . doEvaluate ( expression , frameId , extraArgs ) ) ; this . _waitAfterStep = waitThenEval . then ( ( ) => { } , ( ) => { } ) ; return waitThenEval ; } private async doEvaluate ( expression , frameId ? , extraArgs ? ) { if ( typeof frameId === 'number' ) { const frame = this . _stackFrames . getFrame ( frameId ) ; if ( ! frame || ! frame . callFrameId ) { return utils . errP ( errors . evalNotAvailableMsg ) ; } return this . evaluateOnCallFrame ( expression , frame , extraArgs ) ; } else { let args = { expression , silent : true , includeCommandLineAPI : true , objectGroup : 'console' , userGesture : true } ; if ( extraArgs ) { args = Object . assign ( args , extraArgs ) ; } return this . globalEvaluate ( args ) ; } } async evaluateOnCallFrame ( expression , frame , extraArgs ? ) { const callFrameId = frame . callFrameId ; let args = { callFrameId , expression , silent : true , includeCommandLineAPI : true , objectGroup : 'console' } ; if ( extraArgs ) { args = Object . assign ( args , extraArgs ) ; } return this . chrome . Debugger . evaluateOnCallFrame ( args ) ; } public setVariable ( args ) { return this . _variablesManager . setVariable ( args ) ; } public async restartFrame ( args ) { const callFrame = this . _stackFrames . getFrame ( args . frameId ) ; if ( ! callFrame || ! callFrame . callFrameId ) { return utils . errP ( errors . noRestartFrame ) ; } try { await this . chrome . Debugger . restartFrame ( { callFrameId : callFrame . callFrameId } ) ; } catch ( _e ) { } this . _expectingStopReason = 'frame_entry' ; return this . chrome . Debugger . stepInto ( { } ) ; } public async completions ( args ) { const text = args . text ; const column = args . column ; const prefix = text . substring ( 0 , column - 1 ) ; let expression ; const dot = prefix . lastIndexOf ( '.' ) ; if ( dot >= 0 ) { expression = prefix . substr ( 0 , dot ) ; } if ( typeof args . frameId === 'number' && ! expression ) { logger . verbose ( `Completions: Returning global completions` ) ; if ( ! this . _stackFrames . getFrame ( args . frameId ) ) { return Promise . reject ( errors . stackFrameNotValid ( ) ) ; } const callFrame = this . _stackFrames . getFrame ( args . frameId ) ; if ( ! callFrame || ! callFrame . callFrameId ) { return { targets : [ ] } ; } const scopeExpandPs = callFrame . scopeChain . map ( scope => new ScopeContainer ( callFrame . callFrameId , undefined , scope . object . objectId ) . expand ( this . _variablesManager ) ) ; return Promise . all ( scopeExpandPs ) . then ( ( variableArrs ) => { const targets = this . getFlatAndUniqueCompletionItems ( variableArrs . map ( variableArr => variableArr . map ( variable => variable . name ) ) ) ; return { targets } ; } ) ; } else { expression = expression || 'this' ; logger . verbose ( `Completions: Returning for expression '${ expression }'` ) ; const getCompletionsFn = `(function(x){var a=[];for(var o=x;o!==null&&typeof o !== 'undefined';o=o.__proto__){a.push(Object.getOwnPropertyNames(o))};return a})(${ expression })` ; const response = await this . waitThenDoEvaluate ( getCompletionsFn , args . frameId , { returnByValue : true } ) ; if ( response . exceptionDetails ) { return { targets : [ ] } ; } else { return { targets : this . getFlatAndUniqueCompletionItems ( response . result . value ) } ; } } } private getFlatAndUniqueCompletionItems ( arrays ) { const set = new Set < string > ( ) ; const items = [ ] ; for ( let i = 0 ; i < arrays . length ; i ++ ) { for ( let name of arrays [ i ] ) { if ( ! isIndexedPropName ( name ) && ! set . has ( name ) ) { set . add ( name ) ; items . push ( { label : < string > name , type : 'property' } ) ; } } } return items ; } private getScriptByUrl ( url ) { return this . _scriptContainer . getScriptByUrl ( url ) ; } public breakpointLocations ( args , _telemetryPropertyCollector ? , requestSeq ? ) { return this . _breakpoints . getBreakpointsLocations ( args , this . _scriptContainer , requestSeq ) ; } }