UNPKG

debug-server-next

Version:

Dev server for hippy-core.

1,194 lines 51.3 kB
/* * 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. */ /* eslint-disable rulesdir/no_underscored_properties */ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; import * as i18n from '../i18n/i18n.js'; import * as Platform from '../platform/platform.js'; import * as Root from '../root/root.js'; import { ScopeRef } from './RemoteObject.js'; import { Events as ResourceTreeModelEvents, ResourceTreeModel } from './ResourceTreeModel.js'; import { RuntimeModel } from './RuntimeModel.js'; import { Script } from './Script.js'; import { Capability, Type } from './Target.js'; import { SDKModel } from './SDKModel.js'; import { SourceMapManager } from './SourceMapManager.js'; const UIStrings = { /** *@description Title of a section in the debugger showing local JavaScript variables. */ local: 'Local', /** *@description Text that refers to closure as a programming term */ closure: 'Closure', /** *@description Noun that represents a section or block of code in the Debugger Model. Shown in the Sources tab, while paused on a breakpoint. */ block: 'Block', /** *@description Label for a group of JavaScript files */ script: 'Script', /** *@description Title of a section in the debugger showing JavaScript variables from the a 'with' *block. Block here means section of code, 'with' refers to a JavaScript programming concept. */ withBlock: '`With` Block', /** *@description Title of a section in the debugger showing JavaScript variables from the global scope. */ global: 'Global', /** *@description Text for a JavaScript module, the programming concept */ module: 'Module', /** *@description Text describing the expression scope in WebAssembly */ expression: 'Expression', }; const str_ = i18n.i18n.registerUIStrings('core/sdk/DebuggerModel.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export function sortAndMergeRanges(locationRanges) { if (locationRanges.length === 0) { return []; } locationRanges.sort(LocationRange.comparator); let prev = locationRanges[0]; const merged = []; for (let i = 1; i < locationRanges.length; ++i) { const current = locationRanges[i]; if (prev.overlap(current)) { const largerEnd = prev.end.compareTo(current.end) > 0 ? prev.end : current.end; prev = new LocationRange(prev.scriptId, prev.start, largerEnd); } else { merged.push(prev); prev = current; } } merged.push(prev); return merged; } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var StepMode; (function (StepMode) { StepMode["StepInto"] = "StepInto"; StepMode["StepOut"] = "StepOut"; StepMode["StepOver"] = "StepOver"; })(StepMode || (StepMode = {})); export class DebuggerModel extends SDKModel { _agent; _runtimeModel; _sourceMapManager; _sourceMapIdToScript; _debuggerPausedDetails; _scripts; _scriptsBySourceURL; _discardableScripts; _continueToLocationCallback; _selectedCallFrame; _debuggerEnabled; _debuggerId; _skipAllPausesTimeout; _beforePausedCallback; _computeAutoStepRangesCallback; _expandCallFramesCallback; _evaluateOnCallFrameCallback; _breakpointResolvedEventTarget; _autoStepOver; _isPausing; constructor(target) { super(target); target.registerDebuggerDispatcher(new DebuggerDispatcher(this)); this._agent = target.debuggerAgent(); this._runtimeModel = target.model(RuntimeModel); this._sourceMapManager = new SourceMapManager(target); this._sourceMapIdToScript = new Map(); this._debuggerPausedDetails = null; this._scripts = new Map(); this._scriptsBySourceURL = new Map(); this._discardableScripts = []; this._continueToLocationCallback = null; this._selectedCallFrame = null; this._debuggerEnabled = false; this._debuggerId = null; this._skipAllPausesTimeout = 0; this._beforePausedCallback = null; this._computeAutoStepRangesCallback = null; this._expandCallFramesCallback = null; this._evaluateOnCallFrameCallback = null; this._breakpointResolvedEventTarget = new Common.ObjectWrapper.ObjectWrapper(); this._autoStepOver = false; this._isPausing = false; Common.Settings.Settings.instance() .moduleSetting('pauseOnExceptionEnabled') .addChangeListener(this._pauseOnExceptionStateChanged, this); Common.Settings.Settings.instance() .moduleSetting('pauseOnCaughtException') .addChangeListener(this._pauseOnExceptionStateChanged, this); Common.Settings.Settings.instance() .moduleSetting('disableAsyncStackTraces') .addChangeListener(this._asyncStackTracesStateChanged, this); Common.Settings.Settings.instance() .moduleSetting('breakpointsActive') .addChangeListener(this._breakpointsActiveChanged, this); if (!target.suspended()) { this._enableDebugger(); } this._sourceMapManager.setEnabled(Common.Settings.Settings.instance().moduleSetting('jsSourceMapsEnabled').get()); Common.Settings.Settings.instance() .moduleSetting('jsSourceMapsEnabled') .addChangeListener(event => this._sourceMapManager.setEnabled(event.data)); const resourceTreeModel = target.model(ResourceTreeModel); if (resourceTreeModel) { resourceTreeModel.addEventListener(ResourceTreeModelEvents.FrameNavigated, this._onFrameNavigated, this); } } static _sourceMapId(executionContextId, sourceURL, sourceMapURL) { if (!sourceMapURL) { return null; } return executionContextId + ':' + sourceURL + ':' + sourceMapURL; } sourceMapManager() { return this._sourceMapManager; } runtimeModel() { return this._runtimeModel; } debuggerEnabled() { return Boolean(this._debuggerEnabled); } async _enableDebugger() { if (this._debuggerEnabled) { return; } 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.Runtime.queryParam('remoteFrontend') || Root.Runtime.Runtime.queryParam('ws'); const maxScriptsCacheSize = isRemoteFrontend ? 10e6 : 100e6; const enablePromise = this._agent.invoke_enable({ maxScriptsCacheSize }); enablePromise.then(this._registerDebugger.bind(this)); this._pauseOnExceptionStateChanged(); this._asyncStackTracesStateChanged(); if (!Common.Settings.Settings.instance().moduleSetting('breakpointsActive').get()) { this._breakpointsActiveChanged(); } if (_scheduledPauseOnAsyncCall) { this._pauseOnAsyncCall(_scheduledPauseOnAsyncCall); } this.dispatchEventToListeners(Events.DebuggerWasEnabled, this); await enablePromise; } async syncDebuggerId() { const isRemoteFrontend = Root.Runtime.Runtime.queryParam('remoteFrontend') || Root.Runtime.Runtime.queryParam('ws'); const maxScriptsCacheSize = isRemoteFrontend ? 10e6 : 100e6; const enablePromise = this._agent.invoke_enable({ maxScriptsCacheSize }); enablePromise.then(this._registerDebugger.bind(this)); return enablePromise; } _onFrameNavigated() { if (DebuggerModel._shouldResyncDebuggerId) { return; } DebuggerModel._shouldResyncDebuggerId = true; } _registerDebugger(response) { if (response.getError()) { return; } const { debuggerId } = response; _debuggerIdToModel.set(debuggerId, this); this._debuggerId = debuggerId; this.dispatchEventToListeners(Events.DebuggerIsReadyToPause, this); } isReadyToPause() { return Boolean(this._debuggerId); } static async modelForDebuggerId(debuggerId) { if (DebuggerModel._shouldResyncDebuggerId) { await DebuggerModel.resyncDebuggerIdForModels(); DebuggerModel._shouldResyncDebuggerId = false; } return _debuggerIdToModel.get(debuggerId) || null; } static async resyncDebuggerIdForModels() { const dbgModels = _debuggerIdToModel.values(); for (const dbgModel of dbgModels) { if (dbgModel.debuggerEnabled()) { await dbgModel.syncDebuggerId(); } } } async _disableDebugger() { if (!this._debuggerEnabled) { return; } this._debuggerEnabled = false; await this._asyncStackTracesStateChanged(); await this._agent.invoke_disable(); this._isPausing = false; this.globalObjectCleared(); this.dispatchEventToListeners(Events.DebuggerWasDisabled); if (typeof this._debuggerId === 'string') { _debuggerIdToModel.delete(this._debuggerId); } this._debuggerId = null; } _skipAllPauses(skip) { if (this._skipAllPausesTimeout) { clearTimeout(this._skipAllPausesTimeout); this._skipAllPausesTimeout = 0; } this._agent.invoke_setSkipAllPauses({ skip }); } skipAllPausesUntilReloadOrTimeout(timeout) { if (this._skipAllPausesTimeout) { clearTimeout(this._skipAllPausesTimeout); } this._agent.invoke_setSkipAllPauses({ skip: true }); // If reload happens before the timeout, the flag will be already unset and the timeout callback won't change anything. this._skipAllPausesTimeout = window.setTimeout(this._skipAllPauses.bind(this, false), timeout); } _pauseOnExceptionStateChanged() { let state; if (!Common.Settings.Settings.instance().moduleSetting('pauseOnExceptionEnabled').get()) { state = "none" /* None */; } else if (Common.Settings.Settings.instance().moduleSetting('pauseOnCaughtException').get()) { state = "all" /* All */; } else { state = "uncaught" /* Uncaught */; } this._agent.invoke_setPauseOnExceptions({ state }); } _asyncStackTracesStateChanged() { const maxAsyncStackChainDepth = 32; const enabled = !Common.Settings.Settings.instance().moduleSetting('disableAsyncStackTraces').get() && this._debuggerEnabled; const maxDepth = enabled ? maxAsyncStackChainDepth : 0; return this._agent.invoke_setAsyncCallStackDepth({ maxDepth }); } _breakpointsActiveChanged() { this._agent.invoke_setBreakpointsActive({ active: Common.Settings.Settings.instance().moduleSetting('breakpointsActive').get() }); } setComputeAutoStepRangesCallback(callback) { this._computeAutoStepRangesCallback = callback; } async _computeAutoStepSkipList(mode) { let ranges = []; if (this._computeAutoStepRangesCallback && this._debuggerPausedDetails) { const [callFrame] = this._debuggerPausedDetails.callFrames; ranges = await this._computeAutoStepRangesCallback.call(null, mode, callFrame); } const skipList = ranges.map(location => new LocationRange(location.start.scriptId, new ScriptPosition(location.start.lineNumber, location.start.columnNumber), new ScriptPosition(location.end.lineNumber, location.end.columnNumber))); return sortAndMergeRanges(skipList).map(x => x.payload()); } async stepInto() { const skipList = await this._computeAutoStepSkipList(StepMode.StepInto); this._agent.invoke_stepInto({ breakOnAsyncCall: false, skipList }); } async stepOver() { // Mark that in case of auto-stepping, we should be doing // step-over instead of step-in. this._autoStepOver = true; const skipList = await this._computeAutoStepSkipList(StepMode.StepOver); this._agent.invoke_stepOver({ skipList }); } async stepOut() { const skipList = await this._computeAutoStepSkipList(StepMode.StepOut); if (skipList.length !== 0) { this._agent.invoke_stepOver({ skipList }); } else { this._agent.invoke_stepOut(); } } scheduleStepIntoAsync() { this._computeAutoStepSkipList(StepMode.StepInto).then(skipList => { this._agent.invoke_stepInto({ breakOnAsyncCall: true, skipList }); }); } resume() { this._agent.invoke_resume({ terminateOnResume: false }); this._isPausing = false; } pause() { this._isPausing = true; this._skipAllPauses(false); this._agent.invoke_pause(); } _pauseOnAsyncCall(parentStackTraceId) { return this._agent.invoke_pauseOnAsyncCall({ parentStackTraceId: parentStackTraceId }); } async setBreakpointByURL(url, lineNumber, columnNumber, condition) { // Convert file url to node-js path. let urlRegex; if (this.target().type() === Type.Node && url.startsWith('file://')) { const platformPath = Common.ParsedURL.ParsedURL.urlToPlatformPath(url, Host.Platform.isWin()); urlRegex = `${Platform.StringUtilities.escapeForRegExp(platformPath)}|${Platform.StringUtilities.escapeForRegExp(url)}`; } // 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 || 0, minColumnNumber); const response = await this._agent.invoke_setBreakpointByUrl({ lineNumber: lineNumber, url: urlRegex ? undefined : url, urlRegex: urlRegex, columnNumber: columnNumber, condition: condition, }); if (response.getError()) { return { locations: [], breakpointId: null }; } let locations = []; if (response.locations) { locations = response.locations.map(payload => Location.fromPayload(this, payload)); } return { locations, breakpointId: response.breakpointId }; } 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.getError(); 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, breakpointId: response.breakpointId }; } 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.getError()) { return { breakpointId: null, locations: [] }; } let actualLocation = []; if (response.actualLocation) { actualLocation = [Location.fromPayload(this, response.actualLocation)]; } return { locations: actualLocation, breakpointId: response.breakpointId }; } async removeBreakpoint(breakpointId) { const response = await this._agent.invoke_removeBreakpoint({ breakpointId }); if (response.getError()) { console.error('Failed to remove breakpoint: ' + response.getError()); } } async getPossibleBreakpoints(startLocation, endLocation, restrictToFunction) { const response = await this._agent.invoke_getPossibleBreakpoints({ start: startLocation.payload(), end: endLocation ? endLocation.payload() : undefined, restrictToFunction: restrictToFunction, }); if (response.getError() || !response.locations) { return []; } return response.locations.map(location => BreakLocation.fromPayload(this, location)); } async fetchAsyncStackTrace(stackId) { const response = await this._agent.invoke_getStackTrace({ stackTraceId: stackId }); return response.getError() ? null : response.stackTrace; } _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._discardableScripts = []; this._autoStepOver = false; } scripts() { return Array.from(this._scripts.values()); } scriptForId(scriptId) { return this._scripts.get(scriptId) || null; } scriptsForSourceURL(sourceURL) { if (!sourceURL) { return []; } return this._scriptsBySourceURL.get(sourceURL) || []; } scriptsForExecutionContext(executionContext) { const result = []; for (const script of this._scripts.values()) { if (script.executionContextId === executionContext.id) { result.push(script); } } return result; } setScriptSource(scriptId, newSource, callback) { const script = this._scripts.get(scriptId); if (script) { script.editSource(newSource, this._didEditScriptSource.bind(this, scriptId, newSource, callback)); } } _didEditScriptSource(scriptId, newSource, callback, error, exceptionDetails, callFrames, asyncStackTrace, asyncStackTraceId, needsStepIn) { callback(error, exceptionDetails); if (needsStepIn) { this.stepInto(); return; } if (!error && callFrames && callFrames.length && this._debuggerPausedDetails) { this._pausedScript(callFrames, this._debuggerPausedDetails.reason, this._debuggerPausedDetails.auxData, this._debuggerPausedDetails.breakpointIds, asyncStackTrace, asyncStackTraceId); } } get callFrames() { return this._debuggerPausedDetails ? this._debuggerPausedDetails.callFrames : null; } debuggerPausedDetails() { return this._debuggerPausedDetails; } _setDebuggerPausedDetails(debuggerPausedDetails) { if (debuggerPausedDetails) { this._isPausing = false; this._debuggerPausedDetails = debuggerPausedDetails; if (this._beforePausedCallback) { if (!this._beforePausedCallback.call(null, 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); this.setSelectedCallFrame(debuggerPausedDetails.callFrames[0]); } else { this._isPausing = false; this._debuggerPausedDetails = null; this.setSelectedCallFrame(null); } return true; } setBeforePausedCallback(callback) { this._beforePausedCallback = callback; } setExpandCallFramesCallback(callback) { this._expandCallFramesCallback = callback; } setEvaluateOnCallFrameCallback(callback) { this._evaluateOnCallFrameCallback = callback; } 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. _scheduledPauseOnAsyncCall = asyncCallStackTraceId; const promises = []; for (const model of _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 (this._expandCallFramesCallback) { pausedDetails.callFrames = await this._expandCallFramesCallback.call(null, pausedDetails.callFrames); } if (this._continueToLocationCallback) { const callback = this._continueToLocationCallback; this._continueToLocationCallback = null; if (callback(pausedDetails)) { return; } } if (!this._setDebuggerPausedDetails(pausedDetails)) { if (this._autoStepOver) { this.stepOver(); } else { this.stepInto(); } } else { Common.EventTarget.fireEvent('DevTools.DebuggerPaused'); } _scheduledPauseOnAsyncCall = null; } _resumedScript() { this._setDebuggerPausedDetails(null); this.dispatchEventToListeners(Events.DebuggerResumed, this); } _parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any executionContextId, hash, executionContextAuxData, isLiveEdit, sourceMapURL, hasSourceURLComment, hasSyntaxError, length, isModule, originStackTrace, codeOffset, scriptLanguage, debugSymbols, embedderName) { const knownScript = this._scripts.get(scriptId); if (knownScript) { return knownScript; } let isContentScript = false; if (executionContextAuxData && ('isDefault' in executionContextAuxData)) { isContentScript = !executionContextAuxData['isDefault']; } const script = new Script(this, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, isContentScript, isLiveEdit, sourceMapURL, hasSourceURLComment, length, isModule, originStackTrace, codeOffset, scriptLanguage, debugSymbols, embedderName); this._registerScript(script); this.dispatchEventToListeners(Events.ParsedScriptSource, script); const sourceMapId = 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; } setSourceMapURL(script, newSourceMapURL) { let sourceMapId = 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 = DebuggerModel._sourceMapId(script.executionContextId, script.sourceURL, script.sourceMapURL); if (!sourceMapId) { return; } this._sourceMapIdToScript.set(sourceMapId, script); this._sourceMapManager.attachSourceMap(script, script.sourceURL, script.sourceMapURL); } executionContextDestroyed(executionContext) { const sourceMapIds = Array.from(this._sourceMapIdToScript.keys()); for (const sourceMapId of sourceMapIds) { const script = this._sourceMapIdToScript.get(sourceMapId); if (script && script.executionContextId === executionContext.id) { this._sourceMapIdToScript.delete(sourceMapId); this._sourceMapManager.detachSourceMap(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); } _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); } } createRawLocation(script, lineNumber, columnNumber, inlineFrameIndex) { return this.createRawLocationByScriptId(script.scriptId, lineNumber, columnNumber, inlineFrameIndex); } createRawLocationByURL(sourceURL, lineNumber, columnNumber, inlineFrameIndex) { for (const script of this._scriptsBySourceURL.get(sourceURL) || []) { if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && columnNumber !== undefined && script.columnOffset > columnNumber)) { continue; } if (script.endLine < lineNumber || (script.endLine === lineNumber && columnNumber !== undefined && script.endColumn <= columnNumber)) { continue; } return new Location(this, script.scriptId, lineNumber, columnNumber, inlineFrameIndex); } return null; } createRawLocationByScriptId(scriptId, lineNumber, columnNumber, inlineFrameIndex) { return new Location(this, scriptId, lineNumber, columnNumber, inlineFrameIndex); } createRawLocationsByStackTrace(stackTrace) { const frames = []; let current = stackTrace; while (current) { for (const frame of current.callFrames) { frames.push(frame); } current = current.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; } isPaused() { return Boolean(this.debuggerPausedDetails()); } isPausing() { return this._isPausing; } setSelectedCallFrame(callFrame) { if (this._selectedCallFrame === callFrame) { return; } this._selectedCallFrame = callFrame; this.dispatchEventToListeners(Events.CallFrameSelected, this); } selectedCallFrame() { return this._selectedCallFrame; } async evaluateOnSelectedCallFrame(options) { const callFrame = this.selectedCallFrame(); if (!callFrame) { throw new Error('No call frame selected'); } return callFrame.evaluate(options); } functionDetailsPromise(remoteObject) { return remoteObject.getAllProperties(false /* accessorPropertiesOnly */, false /* generatePreview */) .then(buildDetails.bind(this)); 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; } } } let debuggerLocation = null; if (location) { debuggerLocation = this.createRawLocationByScriptId(location.value.scriptId, location.value.lineNumber, location.value.columnNumber); } return { location: debuggerLocation, functionName: functionName ? functionName.value : '' }; } } async setVariableValue(scopeNumber, variableName, newValue, callFrameId) { const response = await this._agent.invoke_setVariableValue({ scopeNumber, variableName, newValue, callFrameId }); const error = response.getError(); if (error) { console.error(error); } return error; } addBreakpointListener(breakpointId, listener, thisObject) { this._breakpointResolvedEventTarget.addEventListener(breakpointId, listener, thisObject); } removeBreakpointListener(breakpointId, listener, thisObject) { this._breakpointResolvedEventTarget.removeEventListener(breakpointId, listener, thisObject); } async setBlackboxPatterns(patterns) { const response = await this._agent.invoke_setBlackboxPatterns({ patterns }); const error = response.getError(); if (error) { console.error(error); } return !error; } dispose() { this._sourceMapManager.dispose(); if (this._debuggerId) { _debuggerIdToModel.delete(this._debuggerId); } Common.Settings.Settings.instance() .moduleSetting('pauseOnExceptionEnabled') .removeChangeListener(this._pauseOnExceptionStateChanged, this); Common.Settings.Settings.instance() .moduleSetting('pauseOnCaughtException') .removeChangeListener(this._pauseOnExceptionStateChanged, this); Common.Settings.Settings.instance() .moduleSetting('disableAsyncStackTraces') .removeChangeListener(this._asyncStackTracesStateChanged, this); } async suspendModel() { await this._disableDebugger(); } async resumeModel() { await this._enableDebugger(); } static _shouldResyncDebuggerId = false; } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention export const _debuggerIdToModel = new Map(); // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention export let _scheduledPauseOnAsyncCall = null; /** * Keep these in sync with WebCore::V8Debugger */ // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var PauseOnExceptionsState; (function (PauseOnExceptionsState) { PauseOnExceptionsState["DontPauseOnExceptions"] = "none"; PauseOnExceptionsState["PauseOnAllExceptions"] = "all"; PauseOnExceptionsState["PauseOnUncaughtExceptions"] = "uncaught"; })(PauseOnExceptionsState || (PauseOnExceptionsState = {})); // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var Events; (function (Events) { Events["DebuggerWasEnabled"] = "DebuggerWasEnabled"; Events["DebuggerWasDisabled"] = "DebuggerWasDisabled"; Events["DebuggerPaused"] = "DebuggerPaused"; Events["DebuggerResumed"] = "DebuggerResumed"; Events["ParsedScriptSource"] = "ParsedScriptSource"; Events["FailedToParseScriptSource"] = "FailedToParseScriptSource"; Events["DiscardedAnonymousScriptSource"] = "DiscardedAnonymousScriptSource"; Events["GlobalObjectCleared"] = "GlobalObjectCleared"; Events["CallFrameSelected"] = "CallFrameSelected"; Events["ConsoleCommandEvaluatedInSelectedCallFrame"] = "ConsoleCommandEvaluatedInSelectedCallFrame"; Events["DebuggerIsReadyToPause"] = "DebuggerIsReadyToPause"; })(Events || (Events = {})); class DebuggerDispatcher { _debuggerModel; constructor(debuggerModel) { this._debuggerModel = debuggerModel; } paused({ callFrames, reason, data, hitBreakpoints, asyncStackTrace, asyncStackTraceId, asyncCallStackTraceId }) { if (!this._debuggerModel.debuggerEnabled()) { return; } this._debuggerModel._pausedScript(callFrames, reason, data, hitBreakpoints || [], asyncStackTrace, asyncStackTraceId, asyncCallStackTraceId); } resumed() { if (!this._debuggerModel.debuggerEnabled()) { return; } this._debuggerModel._resumedScript(); } scriptParsed({ scriptId, url, startLine, startColumn, endLine, endColumn, executionContextId, hash, executionContextAuxData, isLiveEdit, sourceMapURL, hasSourceURL, length, isModule, stackTrace, codeOffset, scriptLanguage, debugSymbols, embedderName, }) { if (!this._debuggerModel.debuggerEnabled()) { return; } this._debuggerModel._parsedScriptSource(scriptId, url, startLine, startColumn, endLine, endColumn, executionContextId, hash, executionContextAuxData, Boolean(isLiveEdit), sourceMapURL, Boolean(hasSourceURL), false, length || 0, isModule || null, stackTrace || null, codeOffset || null, scriptLanguage || null, debugSymbols || null, embedderName || null); } scriptFailedToParse({ scriptId, url, startLine, startColumn, endLine, endColumn, executionContextId, hash, executionContextAuxData, sourceMapURL, hasSourceURL, length, isModule, stackTrace, codeOffset, scriptLanguage, embedderName, }) { if (!this._debuggerModel.debuggerEnabled()) { return; } this._debuggerModel._parsedScriptSource(scriptId, url, startLine, startColumn, endLine, endColumn, executionContextId, hash, executionContextAuxData, false, sourceMapURL, Boolean(hasSourceURL), true, length || 0, isModule || null, stackTrace || null, codeOffset || null, scriptLanguage || null, null, embedderName || null); } breakpointResolved({ breakpointId, location }) { if (!this._debuggerModel.debuggerEnabled()) { return; } this._debuggerModel._breakpointResolved(breakpointId, location); } } export class Location { debuggerModel; scriptId; lineNumber; columnNumber; inlineFrameIndex; constructor(debuggerModel, scriptId, lineNumber, columnNumber, inlineFrameIndex) { this.debuggerModel = debuggerModel; this.scriptId = scriptId; this.lineNumber = lineNumber; this.columnNumber = columnNumber || 0; this.inlineFrameIndex = inlineFrameIndex || 0; } static fromPayload(debuggerModel, payload, inlineFrameIndex) { return new Location(debuggerModel, payload.scriptId, payload.lineNumber, payload.columnNumber, inlineFrameIndex); } payload() { return { scriptId: this.scriptId, lineNumber: this.lineNumber, columnNumber: this.columnNumber }; } script() { return this.debuggerModel.scriptForId(this.scriptId); } continueToLocation(pausedCallback) { if (pausedCallback) { this.debuggerModel._continueToLocationCallback = this._paused.bind(this, pausedCallback); } this.debuggerModel._agent.invoke_continueToLocation({ location: this.payload(), targetCallFrames: "current" /* Current */, }); } _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; } id() { return this.debuggerModel.target().id() + ':' + this.scriptId + ':' + this.lineNumber + ':' + this.columnNumber; } } export class ScriptPosition { lineNumber; columnNumber; constructor(lineNumber, columnNumber) { this.lineNumber = lineNumber; this.columnNumber = columnNumber; } payload() { return { lineNumber: this.lineNumber, columnNumber: this.columnNumber }; } compareTo(other) { if (this.lineNumber !== other.lineNumber) { return this.lineNumber - other.lineNumber; } return this.columnNumber - other.columnNumber; } } export class LocationRange { scriptId; start; end; constructor(scriptId, start, end) { this.scriptId = scriptId; this.start = start; this.end = end; } payload() { return { scriptId: this.scriptId, start: this.start.payload(), end: this.end.payload() }; } static comparator(location1, location2) { return location1.compareTo(location2); } compareTo(other) { if (this.scriptId !== other.scriptId) { return this.scriptId > other.scriptId ? 1 : -1; } const startCmp = this.start.compareTo(other.start); if (startCmp) { return startCmp; } return this.end.compareTo(other.end); } overlap(other) { if (this.scriptId !== other.scriptId) { return false; } const startCmp = this.start.compareTo(other.start); if (startCmp < 0) { return this.end.compareTo(other.start) >= 0; } if (startCmp > 0) { return this.start.compareTo(other.end) <= 0; } return true; } } export class BreakLocation extends Location { type; constructor(debuggerModel, scriptId, lineNumber, columnNumber, type) { super(debuggerModel, scriptId, lineNumber, columnNumber); if (type) { this.type = type; } } static fromPayload(debuggerModel, payload) { return new BreakLocation(debuggerModel, payload.scriptId, payload.lineNumber, payload.columnNumber, payload.type); } } export class CallFrame { debuggerModel; _script; _payload; _location; _scopeChain; _localScope; _inlineFrameIndex; _functionName; _functionLocation; _returnValue; warnings = []; constructor(debuggerModel, script, payload, inlineFrameIndex, functionName) { this.debuggerModel = debuggerModel; this._script = script; this._payload = payload; this._location = Location.fromPayload(debuggerModel, payload.location, inlineFrameIndex); this._scopeChain = []; this._localScope = null; this._inlineFrameIndex = inlineFrameIndex || 0; this._functionName = functionName || payload.functionName; for (let i = 0; i < payload.scopeChain.length; ++i) { const scope = new Scope(this, i); this._scopeChain.push(scope); if (scope.type() === "local" /* 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; } 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; } createVirtualCallFrame(inlineFrameIndex, name) { return new CallFrame(this.debuggerModel, this._script, this._payload, inlineFrameIndex, name); } addWarning(warning) { this.warnings.push(warning); } get script() { return this._script; } get id() { return this._payload.callFrameId; } get inlineFrameIndex() { return this._inlineFrameIndex; } scopeChain() { return this._scopeChain; } localScope() { return this._localScope; } thisObject() { return this._payload.this ? this.debuggerModel._runtimeModel.createRemoteObject(this._payload.this) : null; } returnValue() { return this._returnValue; } 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.getError() || evaluateResponse.exceptionDetails) { return null; } const response = await this.debuggerModel._agent.invoke_setReturnValue({ newValue: evaluateResponse.result }); if (response.getError()) { return null; } this._returnValue = this.debuggerModel._runtimeModel.createRemoteObject(evaluateResponse.result); return this._returnValue; } get functionName() { return this._functionName; } location() { return this._location; } functionLocation() { return this._functionLocation || null; } async evaluate(options) { const debuggerModel = this.debuggerModel; const runtimeModel = debuggerModel.runtimeModel(); // Assume backends either support both throwOnSideEffect and timeout options or neither. const needsTerminationOptions = Boolean(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.' }; } if (debuggerModel._evaluateOnCallFrameCallback) { const result = await debuggerModel._evaluateOnCallFrameCallback(this, options); if (result) { return result; } } 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.getError(); if (error) { console.error(error); return { error: error }; } return { object: runtimeModel.createRemoteObject(response.result), exceptionDetails: response.exceptionDetails }; } } export class Scope { _callFrame; _payload; _type; _name; _ordinal; _startLocation; _endLocation; _object; 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; this._object = null; } callFrame() { return this._callFrame; } type() { return this._type; } typeName() { switch (this._type) { case "local" /* Local */: return i18nString(UIStrings.local); case "closure" /* Closure */: return i18nString(UIStrings.closure); case "catch" /* Catch */: return i18n.i18n.lockedString('Catch'); case "block" /* Block */: return i18nString(UIStrings.block); case "script" /* Script */: return i18nString(UIStrings.script); case "with" /* With */: return i18nString(UIStrings.withBlock); case "global" /* Global */: return i18nString(UIStrings.global); case "module" /* Module */: return i18nString(UIStrings.module); case "wasm-expression-stack" /* WasmExpressionStack */: return i18nString(UIStrings.expression); } return ''; } name() { return this._name; } startLocation() { return this._startLocation; } endLocation() { return this._endLocation; } object() { if (this._object) { return this._object; } const runtimeModel = this._callFrame.debuggerModel._runtimeModel; const declarativeScope = this._type !== "with" /* With */ && this._type !== "global" /* Global */; if (declarativeScope) { this._object = runtimeModel.createScopeRemoteObject(this._payload.object, new ScopeRef(this._ordinal, this._callFrame.id)); } else { this._object = runtimeModel.createRemoteObject(this._payload.object); } return this._object; } description() { const declarativeScope = this._type !== "with" /* With */ && this._type !== "global" /* Global */; return declarativeScope ? '' : (this._payload.object.description || ''); } icon() { return undefined; } } export class DebuggerPausedDetails { debuggerModel; callFrames; reason; auxData; breakpointIds; asyncStackTrace; asyncStackTraceId; constructor(debuggerModel, callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId) { this.debuggerModel = debuggerModel; thi