| /* | |
| * Copyright (C) 2010 Google Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: | |
| * | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following disclaimer | |
| * in the documentation and/or other materials provided with the | |
| * distribution. | |
| * * Neither the name of Google Inc. nor the names of its | |
| * contributors may be used to endorse or promote products derived from | |
| * this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| /** | |
| * @unrestricted | |
| */ | |
| export default class DebuggerModel extends SDK.SDKModel { | |
| /** | |
| * @param {!SDK.Target} target | |
| */ | |
| constructor(target) { | |
| super(target); | |
| target.registerDebuggerDispatcher(new DebuggerDispatcher(this)); | |
| this._agent = target.debuggerAgent(); | |
| this._runtimeModel = /** @type {!SDK.RuntimeModel} */ (target.model(SDK.RuntimeModel)); | |
| /** @type {!SDK.SourceMapManager<!SDK.Script>} */ | |
| this._sourceMapManager = new SDK.SourceMapManager(target); | |
| /** @type {!Map<string, !SDK.Script>} */ | |
| this._sourceMapIdToScript = new Map(); | |
| /** @type {?DebuggerPausedDetails} */ | |
| this._debuggerPausedDetails = null; | |
| /** @type {!Map<string, !SDK.Script>} */ | |
| this._scripts = new Map(); | |
| /** @type {!Map.<string, !Array.<!SDK.Script>>} */ | |
| this._scriptsBySourceURL = new Map(); | |
| /** @type {!Array.<!SDK.Script>} */ | |
| this._discardableScripts = []; | |
| /** @type {!Common.Object} */ | |
| this._breakpointResolvedEventTarget = new Common.Object(); | |
| /** @type {boolean} */ | |
| this._autoStepOver = false; | |
| this._isPausing = false; | |
| Common.moduleSetting('pauseOnExceptionEnabled').addChangeListener(this._pauseOnExceptionStateChanged, this); | |
| Common.moduleSetting('pauseOnCaughtException').addChangeListener(this._pauseOnExceptionStateChanged, this); | |
| Common.moduleSetting('disableAsyncStackTraces').addChangeListener(this._asyncStackTracesStateChanged, this); | |
| Common.moduleSetting('breakpointsActive').addChangeListener(this._breakpointsActiveChanged, this); | |
| if (!target.suspended()) { | |
| this._enableDebugger(); | |
| } | |
| /** @type {!Map<string, string>} */ | |
| this._stringMap = new Map(); | |
| this._sourceMapManager.setEnabled(Common.moduleSetting('jsSourceMapsEnabled').get()); | |
| Common.moduleSetting('jsSourceMapsEnabled') | |
| .addChangeListener(event => this._sourceMapManager.setEnabled(/** @type {boolean} */ (event.data))); | |
| } | |
| /** | |
| * @param {string} executionContextId | |
| * @param {string} sourceURL | |
| * @param {?string} sourceMapURL | |
| * @return {?string} | |
| */ | |
| static _sourceMapId(executionContextId, sourceURL, sourceMapURL) { | |
| if (!sourceMapURL) { | |
| return null; | |
| } | |
| return executionContextId + ':' + sourceURL + ':' + sourceMapURL; | |
| } | |
| /** | |
| * @return {!SDK.SourceMapManager<!SDK.Script>} | |
| */ | |
| sourceMapManager() { | |
| return this._sourceMapManager; | |
| } | |
| /** | |
| * @return {!SDK.RuntimeModel} | |
| */ | |
| runtimeModel() { | |
| return this._runtimeModel; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| debuggerEnabled() { | |
| return !!this._debuggerEnabled; | |
| } | |
| /** | |
| * @return {!Promise} | |
| */ | |
| _enableDebugger() { | |
| if (this._debuggerEnabled) { | |
| return Promise.resolve(); | |
| } | |
| this._debuggerEnabled = true; | |
| // Set a limit for the total size of collected script sources retained by debugger. | |
| // 10MB for remote frontends, 100MB for others. | |
| const isRemoteFrontend = Root.Runtime.queryParam('remoteFrontend') || Root.Runtime.queryParam('ws'); | |
| const maxScriptsCacheSize = isRemoteFrontend ? 10e6 : 100e6; | |
| const enablePromise = this._agent.enable(maxScriptsCacheSize); | |
| enablePromise.then(this._registerDebugger.bind(this)); | |
| this._pauseOnExceptionStateChanged(); | |
| this._asyncStackTracesStateChanged(); | |
| if (!Common.moduleSetting('breakpointsActive').get()) { | |
| this._breakpointsActiveChanged(); | |
| } | |
| if (SDK.DebuggerModel._scheduledPauseOnAsyncCall) { | |
| this._pauseOnAsyncCall(SDK.DebuggerModel._scheduledPauseOnAsyncCall); | |
| } | |
| this.dispatchEventToListeners(Events.DebuggerWasEnabled, this); | |
| return enablePromise; | |
| } | |
| /** | |
| * @param {string|null} debuggerId | |
| */ | |
| _registerDebugger(debuggerId) { | |
| if (!debuggerId) { | |
| return; | |
| } | |
| SDK.DebuggerModel._debuggerIdToModel.set(debuggerId, this); | |
| this._debuggerId = debuggerId; | |
| this.dispatchEventToListeners(Events.DebuggerIsReadyToPause, this); | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isReadyToPause() { | |
| return !!this._debuggerId; | |
| } | |
| /** | |
| * @param {string} debuggerId | |
| * @return {?DebuggerModel} | |
| */ | |
| static modelForDebuggerId(debuggerId) { | |
| return SDK.DebuggerModel._debuggerIdToModel.get(debuggerId) || null; | |
| } | |
| /** | |
| * @return {!Promise} | |
| */ | |
| _disableDebugger() { | |
| if (!this._debuggerEnabled) { | |
| return Promise.resolve(); | |
| } | |
| this._debuggerEnabled = false; | |
| const disablePromise = this._agent.disable(); | |
| this._isPausing = false; | |
| this._asyncStackTracesStateChanged(); | |
| this.globalObjectCleared(); | |
| this.dispatchEventToListeners(Events.DebuggerWasDisabled); | |
| SDK.DebuggerModel._debuggerIdToModel.delete(this._debuggerId); | |
| return disablePromise; | |
| } | |
| /** | |
| * @param {boolean} skip | |
| */ | |
| _skipAllPauses(skip) { | |
| if (this._skipAllPausesTimeout) { | |
| clearTimeout(this._skipAllPausesTimeout); | |
| delete this._skipAllPausesTimeout; | |
| } | |
| this._agent.setSkipAllPauses(skip); | |
| } | |
| /** | |
| * @param {number} timeout | |
| */ | |
| skipAllPausesUntilReloadOrTimeout(timeout) { | |
| if (this._skipAllPausesTimeout) { | |
| clearTimeout(this._skipAllPausesTimeout); | |
| } | |
| this._agent.setSkipAllPauses(true); | |
| // If reload happens before the timeout, the flag will be already unset and the timeout callback won't change anything. | |
| this._skipAllPausesTimeout = setTimeout(this._skipAllPauses.bind(this, false), timeout); | |
| } | |
| _pauseOnExceptionStateChanged() { | |
| let state; | |
| if (!Common.moduleSetting('pauseOnExceptionEnabled').get()) { | |
| state = PauseOnExceptionsState.DontPauseOnExceptions; | |
| } else if (Common.moduleSetting('pauseOnCaughtException').get()) { | |
| state = PauseOnExceptionsState.PauseOnAllExceptions; | |
| } else { | |
| state = PauseOnExceptionsState.PauseOnUncaughtExceptions; | |
| } | |
| this._agent.setPauseOnExceptions(state); | |
| } | |
| _asyncStackTracesStateChanged() { | |
| const maxAsyncStackChainDepth = 32; | |
| const enabled = !Common.moduleSetting('disableAsyncStackTraces').get() && this._debuggerEnabled; | |
| this._agent.setAsyncCallStackDepth(enabled ? maxAsyncStackChainDepth : 0); | |
| } | |
| _breakpointsActiveChanged() { | |
| this._agent.setBreakpointsActive(Common.moduleSetting('breakpointsActive').get()); | |
| } | |
| stepInto() { | |
| this._agent.stepInto(); | |
| } | |
| stepOver() { | |
| // Mark that in case of auto-stepping, we should be doing | |
| // step-over instead of step-in. | |
| this._autoStepOver = true; | |
| this._agent.stepOver(); | |
| } | |
| stepOut() { | |
| this._agent.stepOut(); | |
| } | |
| scheduleStepIntoAsync() { | |
| this._agent.invoke_stepInto({breakOnAsyncCall: true}); | |
| } | |
| resume() { | |
| this._agent.resume(); | |
| this._isPausing = false; | |
| } | |
| pause() { | |
| this._isPausing = true; | |
| this._skipAllPauses(false); | |
| this._agent.pause(); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.StackTraceId} parentStackTraceId | |
| * @return {!Promise} | |
| */ | |
| _pauseOnAsyncCall(parentStackTraceId) { | |
| return this._agent.invoke_pauseOnAsyncCall({parentStackTraceId: parentStackTraceId}); | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {number} lineNumber | |
| * @param {number=} columnNumber | |
| * @param {string=} condition | |
| * @return {!Promise<!SDK.DebuggerModel.SetBreakpointResult>} | |
| */ | |
| async setBreakpointByURL(url, lineNumber, columnNumber, condition) { | |
| // Convert file url to node-js path. | |
| let urlRegex; | |
| if (this.target().type() === SDK.Target.Type.Node) { | |
| const platformPath = Common.ParsedURL.urlToPlatformPath(url, Host.isWin()); | |
| urlRegex = `${platformPath.escapeForRegExp()}|${url.escapeForRegExp()}`; | |
| } | |
| // Adjust column if needed. | |
| let minColumnNumber = 0; | |
| const scripts = this._scriptsBySourceURL.get(url) || []; | |
| for (let i = 0, l = scripts.length; i < l; ++i) { | |
| const script = scripts[i]; | |
| if (lineNumber === script.lineOffset) { | |
| minColumnNumber = minColumnNumber ? Math.min(minColumnNumber, script.columnOffset) : script.columnOffset; | |
| } | |
| } | |
| columnNumber = Math.max(columnNumber, minColumnNumber); | |
| const response = await this._agent.invoke_setBreakpointByUrl({ | |
| lineNumber: lineNumber, | |
| url: urlRegex ? undefined : url, | |
| urlRegex: urlRegex, | |
| columnNumber: columnNumber, | |
| condition: condition | |
| }); | |
| if (response[Protocol.Error]) { | |
| return {locations: [], breakpointId: null}; | |
| } | |
| let locations = []; | |
| if (response.locations) { | |
| locations = response.locations.map(payload => Location.fromPayload(this, payload)); | |
| } | |
| return {locations: locations, breakpointId: response.breakpointId}; | |
| } | |
| /** | |
| * @param {string} scriptId | |
| * @param {string} scriptHash | |
| * @param {number} lineNumber | |
| * @param {number=} columnNumber | |
| * @param {string=} condition | |
| * @return {!Promise<!SDK.DebuggerModel.SetBreakpointResult>} | |
| */ | |
| async setBreakpointInAnonymousScript(scriptId, scriptHash, lineNumber, columnNumber, condition) { | |
| const response = await this._agent.invoke_setBreakpointByUrl( | |
| {lineNumber: lineNumber, scriptHash: scriptHash, columnNumber: columnNumber, condition: condition}); | |
| const error = response[Protocol.Error]; | |
| if (error) { | |
| // Old V8 backend doesn't support scriptHash argument. | |
| if (error !== 'Either url or urlRegex must be specified.') { | |
| return {locations: [], breakpointId: null}; | |
| } | |
| return this._setBreakpointBySourceId(scriptId, lineNumber, columnNumber, condition); | |
| } | |
| let locations = []; | |
| if (response.locations) { | |
| locations = response.locations.map(payload => Location.fromPayload(this, payload)); | |
| } | |
| return {locations: locations, breakpointId: response.breakpointId}; | |
| } | |
| /** | |
| * @param {string} scriptId | |
| * @param {number} lineNumber | |
| * @param {number=} columnNumber | |
| * @param {string=} condition | |
| * @return {!Promise<!SDK.DebuggerModel.SetBreakpointResult>} | |
| */ | |
| async _setBreakpointBySourceId(scriptId, lineNumber, columnNumber, condition) { | |
| // This method is required for backward compatibility with V8 before 6.3.275. | |
| const response = await this._agent.invoke_setBreakpoint( | |
| {location: {scriptId: scriptId, lineNumber: lineNumber, columnNumber: columnNumber}, condition: condition}); | |
| if (response[Protocol.Error]) { | |
| return {breakpointId: null, locations: []}; | |
| } | |
| let actualLocation = []; | |
| if (response.actualLocation) { | |
| actualLocation = [Location.fromPayload(this, response.actualLocation)]; | |
| } | |
| return {locations: actualLocation, breakpointId: response.breakpointId}; | |
| } | |
| /** | |
| * @param {!Protocol.Debugger.BreakpointId} breakpointId | |
| * @return {!Promise} | |
| */ | |
| async removeBreakpoint(breakpointId) { | |
| const response = await this._agent.invoke_removeBreakpoint({breakpointId}); | |
| if (response[Protocol.Error]) { | |
| console.error('Failed to remove breakpoint: ' + response[Protocol.Error]); | |
| } | |
| } | |
| /** | |
| * @param {!Location} startLocation | |
| * @param {?Location} endLocation | |
| * @param {boolean} restrictToFunction | |
| * @return {!Promise<!Array<!BreakLocation>>} | |
| */ | |
| async getPossibleBreakpoints(startLocation, endLocation, restrictToFunction) { | |
| const response = await this._agent.invoke_getPossibleBreakpoints({ | |
| start: startLocation.payload(), | |
| end: endLocation ? endLocation.payload() : undefined, | |
| restrictToFunction: restrictToFunction | |
| }); | |
| if (response[Protocol.Error] || !response.locations) { | |
| return []; | |
| } | |
| return response.locations.map(location => BreakLocation.fromPayload(this, location)); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.StackTraceId} stackId | |
| * @return {!Promise<?Protocol.Runtime.StackTrace>} | |
| */ | |
| async fetchAsyncStackTrace(stackId) { | |
| const response = await this._agent.invoke_getStackTrace({stackTraceId: stackId}); | |
| return response[Protocol.Error] ? null : response.stackTrace; | |
| } | |
| /** | |
| * @param {!Protocol.Debugger.BreakpointId} breakpointId | |
| * @param {!Protocol.Debugger.Location} location | |
| */ | |
| _breakpointResolved(breakpointId, location) { | |
| this._breakpointResolvedEventTarget.dispatchEventToListeners(breakpointId, Location.fromPayload(this, location)); | |
| } | |
| globalObjectCleared() { | |
| this._setDebuggerPausedDetails(null); | |
| this._reset(); | |
| // TODO(dgozman): move clients to ExecutionContextDestroyed/ScriptCollected events. | |
| this.dispatchEventToListeners(Events.GlobalObjectCleared, this); | |
| } | |
| _reset() { | |
| for (const scriptWithSourceMap of this._sourceMapIdToScript.values()) { | |
| this._sourceMapManager.detachSourceMap(scriptWithSourceMap); | |
| } | |
| this._sourceMapIdToScript.clear(); | |
| this._scripts.clear(); | |
| this._scriptsBySourceURL.clear(); | |
| this._stringMap.clear(); | |
| this._discardableScripts = []; | |
| this._autoStepOver = false; | |
| } | |
| /** | |
| * @return {!Array<!SDK.Script>} | |
| */ | |
| scripts() { | |
| return Array.from(this._scripts.values()); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @return {?SDK.Script} | |
| */ | |
| scriptForId(scriptId) { | |
| return this._scripts.get(scriptId) || null; | |
| } | |
| /** | |
| * @return {!Array.<!SDK.Script>} | |
| */ | |
| scriptsForSourceURL(sourceURL) { | |
| if (!sourceURL) { | |
| return []; | |
| } | |
| return this._scriptsBySourceURL.get(sourceURL) || []; | |
| } | |
| /** | |
| * @param {!SDK.ExecutionContext} executionContext | |
| * @return {!Array<!SDK.Script>} | |
| */ | |
| scriptsForExecutionContext(executionContext) { | |
| const result = []; | |
| for (const script of this._scripts.values()) { | |
| if (script.executionContextId === executionContext.id) { | |
| result.push(script); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {string} newSource | |
| * @param {function(?Protocol.Error, !Protocol.Runtime.ExceptionDetails=)} callback | |
| */ | |
| setScriptSource(scriptId, newSource, callback) { | |
| this._scripts.get(scriptId).editSource( | |
| newSource, this._didEditScriptSource.bind(this, scriptId, newSource, callback)); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {string} newSource | |
| * @param {function(?Protocol.Error, !Protocol.Runtime.ExceptionDetails=)} callback | |
| * @param {?Protocol.Error} error | |
| * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | |
| * @param {!Array.<!Protocol.Debugger.CallFrame>=} callFrames | |
| * @param {!Protocol.Runtime.StackTrace=} asyncStackTrace | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncStackTraceId | |
| * @param {boolean=} needsStepIn | |
| */ | |
| _didEditScriptSource( | |
| scriptId, newSource, callback, error, exceptionDetails, callFrames, asyncStackTrace, asyncStackTraceId, | |
| needsStepIn) { | |
| callback(error, exceptionDetails); | |
| if (needsStepIn) { | |
| this.stepInto(); | |
| return; | |
| } | |
| if (!error && callFrames && callFrames.length) { | |
| this._pausedScript( | |
| callFrames, this._debuggerPausedDetails.reason, this._debuggerPausedDetails.auxData, | |
| this._debuggerPausedDetails.breakpointIds, asyncStackTrace, asyncStackTraceId); | |
| } | |
| } | |
| /** | |
| * @return {?Array.<!CallFrame>} | |
| */ | |
| get callFrames() { | |
| return this._debuggerPausedDetails ? this._debuggerPausedDetails.callFrames : null; | |
| } | |
| /** | |
| * @return {?DebuggerPausedDetails} | |
| */ | |
| debuggerPausedDetails() { | |
| return this._debuggerPausedDetails; | |
| } | |
| /** | |
| * @param {?DebuggerPausedDetails} debuggerPausedDetails | |
| * @return {boolean} | |
| */ | |
| _setDebuggerPausedDetails(debuggerPausedDetails) { | |
| this._isPausing = false; | |
| this._debuggerPausedDetails = debuggerPausedDetails; | |
| if (this._debuggerPausedDetails) { | |
| if (this._beforePausedCallback) { | |
| if (!this._beforePausedCallback.call(null, this._debuggerPausedDetails)) { | |
| return false; | |
| } | |
| } | |
| // If we resolved a location in auto-stepping callback, reset the | |
| // step-over marker. | |
| this._autoStepOver = false; | |
| this.dispatchEventToListeners(Events.DebuggerPaused, this); | |
| } | |
| if (debuggerPausedDetails) { | |
| this.setSelectedCallFrame(debuggerPausedDetails.callFrames[0]); | |
| } else { | |
| this.setSelectedCallFrame(null); | |
| } | |
| return true; | |
| } | |
| /** | |
| * @param {?function(!DebuggerPausedDetails):boolean} callback | |
| */ | |
| setBeforePausedCallback(callback) { | |
| this._beforePausedCallback = callback; | |
| } | |
| /** | |
| * @param {!Array.<!Protocol.Debugger.CallFrame>} callFrames | |
| * @param {string} reason | |
| * @param {!Object|undefined} auxData | |
| * @param {!Array.<string>} breakpointIds | |
| * @param {!Protocol.Runtime.StackTrace=} asyncStackTrace | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncStackTraceId | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncCallStackTraceId | |
| */ | |
| async _pausedScript( | |
| callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId, asyncCallStackTraceId) { | |
| if (asyncCallStackTraceId) { | |
| // Note: this is only to support old backends. Newer ones do not send asyncCallStackTraceId. | |
| SDK.DebuggerModel._scheduledPauseOnAsyncCall = asyncCallStackTraceId; | |
| const promises = []; | |
| for (const model of SDK.DebuggerModel._debuggerIdToModel.values()) { | |
| promises.push(model._pauseOnAsyncCall(asyncCallStackTraceId)); | |
| } | |
| await Promise.all(promises); | |
| this.resume(); | |
| return; | |
| } | |
| const pausedDetails = | |
| new DebuggerPausedDetails(this, callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId); | |
| if (pausedDetails && this._continueToLocationCallback) { | |
| const callback = this._continueToLocationCallback; | |
| delete this._continueToLocationCallback; | |
| if (callback(pausedDetails)) { | |
| return; | |
| } | |
| } | |
| if (!this._setDebuggerPausedDetails(pausedDetails)) { | |
| if (this._autoStepOver) { | |
| this._agent.stepOver(); | |
| } else { | |
| this._agent.stepInto(); | |
| } | |
| } | |
| SDK.DebuggerModel._scheduledPauseOnAsyncCall = null; | |
| } | |
| _resumedScript() { | |
| this._setDebuggerPausedDetails(null); | |
| this.dispatchEventToListeners(Events.DebuggerResumed, this); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {string} sourceURL | |
| * @param {number} startLine | |
| * @param {number} startColumn | |
| * @param {number} endLine | |
| * @param {number} endColumn | |
| * @param {!Protocol.Runtime.ExecutionContextId} executionContextId | |
| * @param {string} hash | |
| * @param {*|undefined} executionContextAuxData | |
| * @param {boolean} isLiveEdit | |
| * @param {string|undefined} sourceMapURL | |
| * @param {boolean} hasSourceURLComment | |
| * @param {boolean} hasSyntaxError | |
| * @param {number} length | |
| * @param {?Protocol.Runtime.StackTrace} originStackTrace | |
| * @return {!SDK.Script} | |
| */ | |
| _parsedScriptSource( | |
| scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | |
| executionContextAuxData, isLiveEdit, sourceMapURL, hasSourceURLComment, hasSyntaxError, length, | |
| originStackTrace) { | |
| if (this._scripts.has(scriptId)) { | |
| return this._scripts.get(scriptId); | |
| } | |
| let isContentScript = false; | |
| if (executionContextAuxData && ('isDefault' in executionContextAuxData)) { | |
| isContentScript = !executionContextAuxData['isDefault']; | |
| } | |
| sourceURL = this._internString(sourceURL); | |
| const script = new SDK.Script( | |
| this, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, | |
| this._internString(hash), isContentScript, isLiveEdit, sourceMapURL, hasSourceURLComment, length, | |
| originStackTrace); | |
| this._registerScript(script); | |
| this.dispatchEventToListeners(Events.ParsedScriptSource, script); | |
| const sourceMapId = | |
| SDK.DebuggerModel._sourceMapId(script.executionContextId, script.sourceURL, script.sourceMapURL); | |
| if (sourceMapId && !hasSyntaxError) { | |
| // Consecutive script evaluations in the same execution context with the same sourceURL | |
| // and sourceMappingURL should result in source map reloading. | |
| const previousScript = this._sourceMapIdToScript.get(sourceMapId); | |
| if (previousScript) { | |
| this._sourceMapManager.detachSourceMap(previousScript); | |
| } | |
| this._sourceMapIdToScript.set(sourceMapId, script); | |
| this._sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL); | |
| } | |
| const isDiscardable = hasSyntaxError && script.isAnonymousScript(); | |
| if (isDiscardable) { | |
| this._discardableScripts.push(script); | |
| this._collectDiscardedScripts(); | |
| } | |
| return script; | |
| } | |
| /** | |
| * @param {!SDK.Script} script | |
| * @param {string} newSourceMapURL | |
| */ | |
| setSourceMapURL(script, newSourceMapURL) { | |
| let sourceMapId = SDK.DebuggerModel._sourceMapId(script.executionContextId, script.sourceURL, script.sourceMapURL); | |
| if (sourceMapId && this._sourceMapIdToScript.get(sourceMapId) === script) { | |
| this._sourceMapIdToScript.delete(sourceMapId); | |
| } | |
| this._sourceMapManager.detachSourceMap(script); | |
| script.sourceMapURL = newSourceMapURL; | |
| sourceMapId = SDK.DebuggerModel._sourceMapId(script.executionContextId, script.sourceURL, script.sourceMapURL); | |
| if (!sourceMapId) { | |
| return; | |
| } | |
| this._sourceMapIdToScript.set(sourceMapId, script); | |
| this._sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL); | |
| } | |
| /** | |
| * @param {!SDK.ExecutionContext} executionContext | |
| */ | |
| executionContextDestroyed(executionContext) { | |
| const sourceMapIds = Array.from(this._sourceMapIdToScript.keys()); | |
| for (const sourceMapId of sourceMapIds) { | |
| const script = this._sourceMapIdToScript.get(sourceMapId); | |
| if (script.executionContextId === executionContext.id) { | |
| this._sourceMapIdToScript.delete(sourceMapId); | |
| this._sourceMapManager.detachSourceMap(script); | |
| } | |
| } | |
| } | |
| /** | |
| * @param {!SDK.Script} script | |
| */ | |
| _registerScript(script) { | |
| this._scripts.set(script.scriptId, script); | |
| if (script.isAnonymousScript()) { | |
| return; | |
| } | |
| let scripts = this._scriptsBySourceURL.get(script.sourceURL); | |
| if (!scripts) { | |
| scripts = []; | |
| this._scriptsBySourceURL.set(script.sourceURL, scripts); | |
| } | |
| scripts.push(script); | |
| } | |
| /** | |
| * @param {!SDK.Script} script | |
| */ | |
| _unregisterScript(script) { | |
| console.assert(script.isAnonymousScript()); | |
| this._scripts.delete(script.scriptId); | |
| } | |
| _collectDiscardedScripts() { | |
| if (this._discardableScripts.length < 1000) { | |
| return; | |
| } | |
| const scriptsToDiscard = this._discardableScripts.splice(0, 100); | |
| for (const script of scriptsToDiscard) { | |
| this._unregisterScript(script); | |
| this.dispatchEventToListeners(Events.DiscardedAnonymousScriptSource, script); | |
| } | |
| } | |
| /** | |
| * @param {!SDK.Script} script | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| * @return {?Location} | |
| */ | |
| createRawLocation(script, lineNumber, columnNumber) { | |
| return new Location(this, script.scriptId, lineNumber, columnNumber); | |
| } | |
| /** | |
| * @param {string} sourceURL | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| * @return {?Location} | |
| */ | |
| createRawLocationByURL(sourceURL, lineNumber, columnNumber) { | |
| let closestScript = null; | |
| const scripts = this._scriptsBySourceURL.get(sourceURL) || []; | |
| for (let i = 0, l = scripts.length; i < l; ++i) { | |
| const script = scripts[i]; | |
| if (!closestScript) { | |
| closestScript = script; | |
| } | |
| if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber)) { | |
| continue; | |
| } | |
| if (script.endLine < lineNumber || (script.endLine === lineNumber && script.endColumn <= columnNumber)) { | |
| continue; | |
| } | |
| closestScript = script; | |
| break; | |
| } | |
| return closestScript ? new Location(this, closestScript.scriptId, lineNumber, columnNumber) : null; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| * @return {?Location} | |
| */ | |
| createRawLocationByScriptId(scriptId, lineNumber, columnNumber) { | |
| const script = this.scriptForId(scriptId); | |
| return script ? this.createRawLocation(script, lineNumber, columnNumber) : null; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.StackTrace} stackTrace | |
| * @return {!Array<!Location>} | |
| */ | |
| createRawLocationsByStackTrace(stackTrace) { | |
| const frames = []; | |
| while (stackTrace) { | |
| for (const frame of stackTrace.callFrames) { | |
| frames.push(frame); | |
| } | |
| stackTrace = stackTrace.parent; | |
| } | |
| const rawLocations = []; | |
| for (const frame of frames) { | |
| const rawLocation = this.createRawLocationByScriptId(frame.scriptId, frame.lineNumber, frame.columnNumber); | |
| if (rawLocation) { | |
| rawLocations.push(rawLocation); | |
| } | |
| } | |
| return rawLocations; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isPaused() { | |
| return !!this.debuggerPausedDetails(); | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isPausing() { | |
| return this._isPausing; | |
| } | |
| /** | |
| * @param {?CallFrame} callFrame | |
| */ | |
| setSelectedCallFrame(callFrame) { | |
| if (this._selectedCallFrame === callFrame) { | |
| return; | |
| } | |
| this._selectedCallFrame = callFrame; | |
| this.dispatchEventToListeners(Events.CallFrameSelected, this); | |
| } | |
| /** | |
| * @return {?CallFrame} | |
| */ | |
| selectedCallFrame() { | |
| return this._selectedCallFrame; | |
| } | |
| /** | |
| * @param {!SDK.RuntimeModel.EvaluationOptions} options | |
| * @return {!Promise<!SDK.RuntimeModel.EvaluationResult>} | |
| */ | |
| evaluateOnSelectedCallFrame(options) { | |
| return this.selectedCallFrame().evaluate(options); | |
| } | |
| /** | |
| * @param {!SDK.RemoteObject} remoteObject | |
| * @return {!Promise<?SDK.DebuggerModel.FunctionDetails>} | |
| */ | |
| functionDetailsPromise(remoteObject) { | |
| return remoteObject.getAllProperties(false /* accessorPropertiesOnly */, false /* generatePreview */) | |
| .then(buildDetails.bind(this)); | |
| /** | |
| * @param {!SDK.GetPropertiesResult} response | |
| * @return {?SDK.DebuggerModel.FunctionDetails} | |
| * @this {!DebuggerModel} | |
| */ | |
| function buildDetails(response) { | |
| if (!response) { | |
| return null; | |
| } | |
| let location = null; | |
| if (response.internalProperties) { | |
| for (const prop of response.internalProperties) { | |
| if (prop.name === '[[FunctionLocation]]') { | |
| location = prop.value; | |
| } | |
| } | |
| } | |
| let functionName = null; | |
| if (response.properties) { | |
| for (const prop of response.properties) { | |
| if (prop.name === 'name' && prop.value && prop.value.type === 'string') { | |
| functionName = prop.value; | |
| } | |
| if (prop.name === 'displayName' && prop.value && prop.value.type === 'string') { | |
| functionName = prop.value; | |
| break; | |
| } | |
| } | |
| } | |
| let debuggerLocation = null; | |
| if (location) { | |
| debuggerLocation = this.createRawLocationByScriptId( | |
| location.value.scriptId, location.value.lineNumber, location.value.columnNumber); | |
| } | |
| return {location: debuggerLocation, functionName: functionName ? functionName.value : ''}; | |
| } | |
| } | |
| /** | |
| * @param {number} scopeNumber | |
| * @param {string} variableName | |
| * @param {!Protocol.Runtime.CallArgument} newValue | |
| * @param {string} callFrameId | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async setVariableValue(scopeNumber, variableName, newValue, callFrameId) { | |
| const response = await this._agent.invoke_setVariableValue({scopeNumber, variableName, newValue, callFrameId}); | |
| const error = response[Protocol.Error]; | |
| if (error) { | |
| console.error(error); | |
| } | |
| return error; | |
| } | |
| /** | |
| * @param {!Protocol.Debugger.BreakpointId} breakpointId | |
| * @param {function(!Common.Event)} listener | |
| * @param {!Object=} thisObject | |
| */ | |
| addBreakpointListener(breakpointId, listener, thisObject) { | |
| this._breakpointResolvedEventTarget.addEventListener(breakpointId, listener, thisObject); | |
| } | |
| /** | |
| * @param {!Protocol.Debugger.BreakpointId} breakpointId | |
| * @param {function(!Common.Event)} listener | |
| * @param {!Object=} thisObject | |
| */ | |
| removeBreakpointListener(breakpointId, listener, thisObject) { | |
| this._breakpointResolvedEventTarget.removeEventListener(breakpointId, listener, thisObject); | |
| } | |
| /** | |
| * @param {!Array<string>} patterns | |
| * @return {!Promise<boolean>} | |
| */ | |
| async setBlackboxPatterns(patterns) { | |
| const response = await this._agent.invoke_setBlackboxPatterns({patterns}); | |
| const error = response[Protocol.Error]; | |
| if (error) { | |
| console.error(error); | |
| } | |
| return !error; | |
| } | |
| /** | |
| * @override | |
| */ | |
| dispose() { | |
| this._sourceMapManager.dispose(); | |
| SDK.DebuggerModel._debuggerIdToModel.delete(this._debuggerId); | |
| Common.moduleSetting('pauseOnExceptionEnabled').removeChangeListener(this._pauseOnExceptionStateChanged, this); | |
| Common.moduleSetting('pauseOnCaughtException').removeChangeListener(this._pauseOnExceptionStateChanged, this); | |
| Common.moduleSetting('disableAsyncStackTraces').removeChangeListener(this._asyncStackTracesStateChanged, this); | |
| } | |
| /** | |
| * @override | |
| * @return {!Promise} | |
| */ | |
| async suspendModel() { | |
| await this._disableDebugger(); | |
| } | |
| /** | |
| * @override | |
| * @return {!Promise} | |
| */ | |
| async resumeModel() { | |
| await this._enableDebugger(); | |
| } | |
| /** | |
| * @param {string} string | |
| * @return {string} string | |
| */ | |
| _internString(string) { | |
| if (!this._stringMap.has(string)) { | |
| this._stringMap.set(string, string); | |
| } | |
| return this._stringMap.get(string); | |
| } | |
| } | |
| /** @type {!Map<string, !SDK.DebuggerModel>} */ | |
| export const _debuggerIdToModel = new Map(); | |
| /** @type {?Protocol.Runtime.StackTraceId} */ | |
| export const _scheduledPauseOnAsyncCall = null; | |
| /** | |
| * Keep these in sync with WebCore::V8Debugger | |
| * | |
| * @enum {string} | |
| */ | |
| export const PauseOnExceptionsState = { | |
| DontPauseOnExceptions: 'none', | |
| PauseOnAllExceptions: 'all', | |
| PauseOnUncaughtExceptions: 'uncaught' | |
| }; | |
| /** @enum {symbol} */ | |
| export const Events = { | |
| DebuggerWasEnabled: Symbol('DebuggerWasEnabled'), | |
| DebuggerWasDisabled: Symbol('DebuggerWasDisabled'), | |
| DebuggerPaused: Symbol('DebuggerPaused'), | |
| DebuggerResumed: Symbol('DebuggerResumed'), | |
| ParsedScriptSource: Symbol('ParsedScriptSource'), | |
| FailedToParseScriptSource: Symbol('FailedToParseScriptSource'), | |
| DiscardedAnonymousScriptSource: Symbol('DiscardedAnonymousScriptSource'), | |
| GlobalObjectCleared: Symbol('GlobalObjectCleared'), | |
| CallFrameSelected: Symbol('CallFrameSelected'), | |
| ConsoleCommandEvaluatedInSelectedCallFrame: Symbol('ConsoleCommandEvaluatedInSelectedCallFrame'), | |
| DebuggerIsReadyToPause: Symbol('DebuggerIsReadyToPause'), | |
| }; | |
| /** @enum {string} */ | |
| export const BreakReason = { | |
| DOM: 'DOM', | |
| EventListener: 'EventListener', | |
| XHR: 'XHR', | |
| Exception: 'exception', | |
| PromiseRejection: 'promiseRejection', | |
| Assert: 'assert', | |
| DebugCommand: 'debugCommand', | |
| OOM: 'OOM', | |
| Other: 'other' | |
| }; | |
| const ContinueToLocationTargetCallFrames = { | |
| Any: 'any', | |
| Current: 'current' | |
| }; | |
| /** | |
| * @extends {Protocol.DebuggerDispatcher} | |
| * @unrestricted | |
| */ | |
| class DebuggerDispatcher { | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| */ | |
| constructor(debuggerModel) { | |
| this._debuggerModel = debuggerModel; | |
| } | |
| /** | |
| * @override | |
| * @param {!Array.<!Protocol.Debugger.CallFrame>} callFrames | |
| * @param {string} reason | |
| * @param {!Object=} auxData | |
| * @param {!Array.<string>=} breakpointIds | |
| * @param {!Protocol.Runtime.StackTrace=} asyncStackTrace | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncStackTraceId | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncCallStackTraceId | |
| */ | |
| paused(callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId, asyncCallStackTraceId) { | |
| this._debuggerModel._pausedScript( | |
| callFrames, reason, auxData, breakpointIds || [], asyncStackTrace, asyncStackTraceId, asyncCallStackTraceId); | |
| } | |
| /** | |
| * @override | |
| */ | |
| resumed() { | |
| this._debuggerModel._resumedScript(); | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {string} sourceURL | |
| * @param {number} startLine | |
| * @param {number} startColumn | |
| * @param {number} endLine | |
| * @param {number} endColumn | |
| * @param {!Protocol.Runtime.ExecutionContextId} executionContextId | |
| * @param {string} hash | |
| * @param {*=} executionContextAuxData | |
| * @param {boolean=} isLiveEdit | |
| * @param {string=} sourceMapURL | |
| * @param {boolean=} hasSourceURL | |
| * @param {boolean=} isModule | |
| * @param {number=} length | |
| * @param {!Protocol.Runtime.StackTrace=} stackTrace | |
| */ | |
| scriptParsed( | |
| scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | |
| executionContextAuxData, isLiveEdit, sourceMapURL, hasSourceURL, isModule, length, stackTrace) { | |
| this._debuggerModel._parsedScriptSource( | |
| scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | |
| executionContextAuxData, !!isLiveEdit, sourceMapURL, !!hasSourceURL, false, length || 0, stackTrace || null); | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Runtime.ScriptId} scriptId | |
| * @param {string} sourceURL | |
| * @param {number} startLine | |
| * @param {number} startColumn | |
| * @param {number} endLine | |
| * @param {number} endColumn | |
| * @param {!Protocol.Runtime.ExecutionContextId} executionContextId | |
| * @param {string} hash | |
| * @param {*=} executionContextAuxData | |
| * @param {string=} sourceMapURL | |
| * @param {boolean=} hasSourceURL | |
| * @param {boolean=} isModule | |
| * @param {number=} length | |
| * @param {!Protocol.Runtime.StackTrace=} stackTrace | |
| */ | |
| scriptFailedToParse( | |
| scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | |
| executionContextAuxData, sourceMapURL, hasSourceURL, isModule, length, stackTrace) { | |
| this._debuggerModel._parsedScriptSource( | |
| scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | |
| executionContextAuxData, false, sourceMapURL, !!hasSourceURL, true, length || 0, stackTrace || null); | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Debugger.BreakpointId} breakpointId | |
| * @param {!Protocol.Debugger.Location} location | |
| */ | |
| breakpointResolved(breakpointId, location) { | |
| this._debuggerModel._breakpointResolved(breakpointId, location); | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class Location { | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {string} scriptId | |
| * @param {number} lineNumber | |
| * @param {number=} columnNumber | |
| */ | |
| constructor(debuggerModel, scriptId, lineNumber, columnNumber) { | |
| this.debuggerModel = debuggerModel; | |
| this.scriptId = scriptId; | |
| this.lineNumber = lineNumber; | |
| this.columnNumber = columnNumber || 0; | |
| } | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {!Protocol.Debugger.Location} payload | |
| * @return {!Location} | |
| */ | |
| static fromPayload(debuggerModel, payload) { | |
| return new Location(debuggerModel, payload.scriptId, payload.lineNumber, payload.columnNumber); | |
| } | |
| /** | |
| * @return {!Protocol.Debugger.Location} | |
| */ | |
| payload() { | |
| return {scriptId: this.scriptId, lineNumber: this.lineNumber, columnNumber: this.columnNumber}; | |
| } | |
| /** | |
| * @return {?SDK.Script} | |
| */ | |
| script() { | |
| return this.debuggerModel.scriptForId(this.scriptId); | |
| } | |
| /** | |
| * @param {function()=} pausedCallback | |
| */ | |
| continueToLocation(pausedCallback) { | |
| if (pausedCallback) { | |
| this.debuggerModel._continueToLocationCallback = this._paused.bind(this, pausedCallback); | |
| } | |
| this.debuggerModel._agent.continueToLocation(this.payload(), ContinueToLocationTargetCallFrames.Current); | |
| } | |
| /** | |
| * @param {function()|undefined} pausedCallback | |
| * @param {!DebuggerPausedDetails} debuggerPausedDetails | |
| * @return {boolean} | |
| */ | |
| _paused(pausedCallback, debuggerPausedDetails) { | |
| const location = debuggerPausedDetails.callFrames[0].location(); | |
| if (location.scriptId === this.scriptId && location.lineNumber === this.lineNumber && | |
| location.columnNumber === this.columnNumber) { | |
| pausedCallback(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| id() { | |
| return this.debuggerModel.target().id() + ':' + this.scriptId + ':' + this.lineNumber + ':' + this.columnNumber; | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class BreakLocation extends Location { | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {string} scriptId | |
| * @param {number} lineNumber | |
| * @param {number=} columnNumber | |
| * @param {!Protocol.Debugger.BreakLocationType=} type | |
| */ | |
| constructor(debuggerModel, scriptId, lineNumber, columnNumber, type) { | |
| super(debuggerModel, scriptId, lineNumber, columnNumber); | |
| if (type) { | |
| this.type = type; | |
| } | |
| } | |
| /** | |
| * @override | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {!Protocol.Debugger.BreakLocation} payload | |
| * @return {!BreakLocation} | |
| */ | |
| static fromPayload(debuggerModel, payload) { | |
| return new BreakLocation(debuggerModel, payload.scriptId, payload.lineNumber, payload.columnNumber, payload.type); | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class CallFrame { | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {!SDK.Script} script | |
| * @param {!Protocol.Debugger.CallFrame} payload | |
| */ | |
| constructor(debuggerModel, script, payload) { | |
| this.debuggerModel = debuggerModel; | |
| this._script = script; | |
| this._payload = payload; | |
| this._location = Location.fromPayload(debuggerModel, payload.location); | |
| this._scopeChain = []; | |
| this._localScope = null; | |
| for (let i = 0; i < payload.scopeChain.length; ++i) { | |
| const scope = new Scope(this, i); | |
| this._scopeChain.push(scope); | |
| if (scope.type() === Protocol.Debugger.ScopeType.Local) { | |
| this._localScope = scope; | |
| } | |
| } | |
| if (payload.functionLocation) { | |
| this._functionLocation = Location.fromPayload(debuggerModel, payload.functionLocation); | |
| } | |
| this._returnValue = | |
| payload.returnValue ? this.debuggerModel._runtimeModel.createRemoteObject(payload.returnValue) : null; | |
| } | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {!Array.<!Protocol.Debugger.CallFrame>} callFrames | |
| * @return {!Array.<!CallFrame>} | |
| */ | |
| static fromPayloadArray(debuggerModel, callFrames) { | |
| const result = []; | |
| for (let i = 0; i < callFrames.length; ++i) { | |
| const callFrame = callFrames[i]; | |
| const script = debuggerModel.scriptForId(callFrame.location.scriptId); | |
| if (script) { | |
| result.push(new CallFrame(debuggerModel, script, callFrame)); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * @return {!SDK.Script} | |
| */ | |
| get script() { | |
| return this._script; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| get id() { | |
| return this._payload.callFrameId; | |
| } | |
| /** | |
| * @return {!Array.<!Scope>} | |
| */ | |
| scopeChain() { | |
| return this._scopeChain; | |
| } | |
| /** | |
| * @return {?Scope} | |
| */ | |
| localScope() { | |
| return this._localScope; | |
| } | |
| /** | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| thisObject() { | |
| return this._payload.this ? this.debuggerModel._runtimeModel.createRemoteObject(this._payload.this) : null; | |
| } | |
| /** | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| returnValue() { | |
| return this._returnValue; | |
| } | |
| /** | |
| * @param {string} expression | |
| * @return {!Promise<?SDK.RemoteObject>} | |
| */ | |
| async setReturnValue(expression) { | |
| if (!this._returnValue) { | |
| return null; | |
| } | |
| const evaluateResponse = await this.debuggerModel._agent.invoke_evaluateOnCallFrame( | |
| {callFrameId: this.id, expression: expression, silent: true, objectGroup: 'backtrace'}); | |
| if (evaluateResponse[Protocol.Error] || evaluateResponse.exceptionDetails) { | |
| return null; | |
| } | |
| const response = await this.debuggerModel._agent.invoke_setReturnValue({newValue: evaluateResponse.result}); | |
| if (response[Protocol.Error]) { | |
| return null; | |
| } | |
| this._returnValue = this.debuggerModel._runtimeModel.createRemoteObject(evaluateResponse.result); | |
| return this._returnValue; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| get functionName() { | |
| return this._payload.functionName; | |
| } | |
| /** | |
| * @return {!Location} | |
| */ | |
| location() { | |
| return this._location; | |
| } | |
| /** | |
| * @return {?Location} | |
| */ | |
| functionLocation() { | |
| return this._functionLocation || null; | |
| } | |
| /** | |
| * @param {!SDK.RuntimeModel.EvaluationOptions} options | |
| * @return {!Promise<!SDK.RuntimeModel.EvaluationResult>} | |
| */ | |
| async evaluate(options) { | |
| const runtimeModel = this.debuggerModel.runtimeModel(); | |
| // Assume backends either support both throwOnSideEffect and timeout options or neither. | |
| const needsTerminationOptions = !!options.throwOnSideEffect || options.timeout !== undefined; | |
| if (needsTerminationOptions && | |
| (runtimeModel.hasSideEffectSupport() === false || | |
| (runtimeModel.hasSideEffectSupport() === null && !await runtimeModel.checkSideEffectSupport()))) { | |
| return {error: 'Side-effect checks not supported by backend.'}; | |
| } | |
| const response = await this.debuggerModel._agent.invoke_evaluateOnCallFrame({ | |
| callFrameId: this.id, | |
| expression: options.expression, | |
| objectGroup: options.objectGroup, | |
| includeCommandLineAPI: options.includeCommandLineAPI, | |
| silent: options.silent, | |
| returnByValue: options.returnByValue, | |
| generatePreview: options.generatePreview, | |
| throwOnSideEffect: options.throwOnSideEffect, | |
| timeout: options.timeout | |
| }); | |
| const error = response[Protocol.Error]; | |
| if (error) { | |
| console.error(error); | |
| return {error: error}; | |
| } | |
| return {object: runtimeModel.createRemoteObject(response.result), exceptionDetails: response.exceptionDetails}; | |
| } | |
| async restart() { | |
| const response = await this.debuggerModel._agent.invoke_restartFrame({callFrameId: this._payload.callFrameId}); | |
| if (!response[Protocol.Error]) { | |
| this.debuggerModel.stepInto(); | |
| } | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class Scope { | |
| /** | |
| * @param {!CallFrame} callFrame | |
| * @param {number} ordinal | |
| */ | |
| constructor(callFrame, ordinal) { | |
| this._callFrame = callFrame; | |
| this._payload = callFrame._payload.scopeChain[ordinal]; | |
| this._type = this._payload.type; | |
| this._name = this._payload.name; | |
| this._ordinal = ordinal; | |
| this._startLocation = | |
| this._payload.startLocation ? Location.fromPayload(callFrame.debuggerModel, this._payload.startLocation) : null; | |
| this._endLocation = | |
| this._payload.endLocation ? Location.fromPayload(callFrame.debuggerModel, this._payload.endLocation) : null; | |
| } | |
| /** | |
| * @return {!CallFrame} | |
| */ | |
| callFrame() { | |
| return this._callFrame; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| type() { | |
| return this._type; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| typeName() { | |
| switch (this._type) { | |
| case Protocol.Debugger.ScopeType.Local: | |
| return Common.UIString('Local'); | |
| case Protocol.Debugger.ScopeType.Closure: | |
| return Common.UIString('Closure'); | |
| case Protocol.Debugger.ScopeType.Catch: | |
| return Common.UIString('Catch'); | |
| case Protocol.Debugger.ScopeType.Block: | |
| return Common.UIString('Block'); | |
| case Protocol.Debugger.ScopeType.Script: | |
| return Common.UIString('Script'); | |
| case Protocol.Debugger.ScopeType.With: | |
| return Common.UIString('With Block'); | |
| case Protocol.Debugger.ScopeType.Global: | |
| return Common.UIString('Global'); | |
| case Protocol.Debugger.ScopeType.Module: | |
| return Common.UIString('Module'); | |
| } | |
| return ''; | |
| } | |
| /** | |
| * @return {string|undefined} | |
| */ | |
| name() { | |
| return this._name; | |
| } | |
| /** | |
| * @return {?Location} | |
| */ | |
| startLocation() { | |
| return this._startLocation; | |
| } | |
| /** | |
| * @return {?Location} | |
| */ | |
| endLocation() { | |
| return this._endLocation; | |
| } | |
| /** | |
| * @return {!SDK.RemoteObject} | |
| */ | |
| object() { | |
| if (this._object) { | |
| return this._object; | |
| } | |
| const runtimeModel = this._callFrame.debuggerModel._runtimeModel; | |
| const declarativeScope = | |
| this._type !== Protocol.Debugger.ScopeType.With && this._type !== Protocol.Debugger.ScopeType.Global; | |
| if (declarativeScope) { | |
| this._object = runtimeModel.createScopeRemoteObject( | |
| this._payload.object, new SDK.ScopeRef(this._ordinal, this._callFrame.id)); | |
| } else { | |
| this._object = runtimeModel.createRemoteObject(this._payload.object); | |
| } | |
| return this._object; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| description() { | |
| const declarativeScope = | |
| this._type !== Protocol.Debugger.ScopeType.With && this._type !== Protocol.Debugger.ScopeType.Global; | |
| return declarativeScope ? '' : (this._payload.object.description || ''); | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class DebuggerPausedDetails { | |
| /** | |
| * @param {!DebuggerModel} debuggerModel | |
| * @param {!Array.<!Protocol.Debugger.CallFrame>} callFrames | |
| * @param {string} reason | |
| * @param {!Object|undefined} auxData | |
| * @param {!Array.<string>} breakpointIds | |
| * @param {!Protocol.Runtime.StackTrace=} asyncStackTrace | |
| * @param {!Protocol.Runtime.StackTraceId=} asyncStackTraceId | |
| */ | |
| constructor(debuggerModel, callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId) { | |
| this.debuggerModel = debuggerModel; | |
| this.callFrames = CallFrame.fromPayloadArray(debuggerModel, callFrames); | |
| this.reason = reason; | |
| this.auxData = auxData; | |
| this.breakpointIds = breakpointIds; | |
| if (asyncStackTrace) { | |
| this.asyncStackTrace = this._cleanRedundantFrames(asyncStackTrace); | |
| } | |
| this.asyncStackTraceId = asyncStackTraceId; | |
| } | |
| /** | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| exception() { | |
| if (this.reason !== BreakReason.Exception && this.reason !== BreakReason.PromiseRejection) { | |
| return null; | |
| } | |
| return this.debuggerModel._runtimeModel.createRemoteObject( | |
| /** @type {!Protocol.Runtime.RemoteObject} */ (this.auxData)); | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.StackTrace} asyncStackTrace | |
| * @return {!Protocol.Runtime.StackTrace} | |
| */ | |
| _cleanRedundantFrames(asyncStackTrace) { | |
| let stack = asyncStackTrace; | |
| let previous = null; | |
| while (stack) { | |
| if (stack.description === 'async function' && stack.callFrames.length) { | |
| stack.callFrames.shift(); | |
| } | |
| if (previous && !stack.callFrames.length) { | |
| previous.parent = stack.parent; | |
| } else { | |
| previous = stack; | |
| } | |
| stack = stack.parent; | |
| } | |
| return asyncStackTrace; | |
| } | |
| } | |
| /* Legacy exported object */ | |
| self.SDK = self.SDK || {}; | |
| /* Legacy exported object */ | |
| SDK = SDK || {}; | |
| /** @constructor */ | |
| SDK.DebuggerModel = DebuggerModel; | |
| /** @enum {string} */ | |
| SDK.DebuggerModel.PauseOnExceptionsState = PauseOnExceptionsState; | |
| /** @enum {symbol} */ | |
| SDK.DebuggerModel.Events = Events; | |
| /** @enum {string} */ | |
| SDK.DebuggerModel.BreakReason = BreakReason; | |
| /** @constructor */ | |
| SDK.DebuggerModel.Location = Location; | |
| /** @constructor */ | |
| SDK.DebuggerModel.BreakLocation = BreakLocation; | |
| /** @constructor */ | |
| SDK.DebuggerModel.CallFrame = CallFrame; | |
| /** @constructor */ | |
| SDK.DebuggerModel.Scope = Scope; | |
| /** @constructor */ | |
| SDK.DebuggerPausedDetails = DebuggerPausedDetails; | |
| SDK.SDKModel.register(SDK.DebuggerModel, SDK.Target.Capability.JS, true); | |
| /** @typedef {{location: ?Location, functionName: string}} */ | |
| SDK.DebuggerModel.FunctionDetails; | |
| /** @typedef {{ | |
| * breakpointId: ?Protocol.Debugger.BreakpointId, | |
| * locations: !Array<!Location> | |
| * }} | |
| */ | |
| SDK.DebuggerModel.SetBreakpointResult; | |
| /** @type {!Map<string, !SDK.DebuggerModel>} */ | |
| SDK.DebuggerModel._debuggerIdToModel = _debuggerIdToModel; | |
| /** @type {?Protocol.Runtime.StackTraceId} */ | |
| SDK.DebuggerModel._scheduledPauseOnAsyncCall = _scheduledPauseOnAsyncCall; |