| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // @ts-nocheck |
| // TODO(crbug.com/1011811): Enable TypeScript compiler checks |
| |
| import * as Common from '../common/common.js'; // eslint-disable-line no-unused-vars |
| import * as SDK from '../sdk/sdk.js'; |
| import * as Workspace from '../workspace/workspace.js'; // eslint-disable-line no-unused-vars |
| |
| import {BlackboxManager} from './BlackboxManager.js'; |
| import {CompilerScriptMapping} from './CompilerScriptMapping.js'; |
| import {DebuggerLanguagePluginManager} from './DebuggerLanguagePlugins.js'; |
| import {DefaultScriptMapping} from './DefaultScriptMapping.js'; |
| import {LiveLocation, LiveLocationPool, LiveLocationWithPool} from './LiveLocation.js'; // eslint-disable-line no-unused-vars |
| import {ResourceMapping} from './ResourceMapping.js'; |
| import {ResourceScriptFile, ResourceScriptMapping} from './ResourceScriptMapping.js'; // eslint-disable-line no-unused-vars |
| |
| /** |
| * @type {!DebuggerWorkspaceBinding} |
| */ |
| let debuggerWorkspaceBindingInstance; |
| |
| /** |
| * @unrestricted |
| * @implements {SDK.SDKModel.SDKModelObserver<!SDK.DebuggerModel.DebuggerModel>} |
| */ |
| export class DebuggerWorkspaceBinding { |
| /** |
| * @private |
| * @param {!SDK.SDKModel.TargetManager} targetManager |
| * @param {!Workspace.Workspace.WorkspaceImpl} workspace |
| */ |
| constructor(targetManager, workspace) { |
| this._workspace = workspace; |
| |
| /** @type {!Array<!DebuggerSourceMapping>} */ |
| this._sourceMappings = []; |
| |
| /** @type {!Map.<!SDK.DebuggerModel.DebuggerModel, !ModelData>} */ |
| this._debuggerModelToData = new Map(); |
| targetManager.addModelListener( |
| SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.GlobalObjectCleared, this._globalObjectCleared, this); |
| targetManager.addModelListener( |
| SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this); |
| targetManager.observeModels(SDK.DebuggerModel.DebuggerModel, this); |
| |
| /** @type {!Set.<!Promise>} */ |
| this._liveLocationPromises = new Set(); |
| } |
| |
| /** |
| * @param {{forceNew: ?boolean, targetManager: ?SDK.SDKModel.TargetManager, workspace: ?Workspace.Workspace.WorkspaceImpl}} opts |
| */ |
| static instance(opts = {forceNew: null, targetManager: null, workspace: null}) { |
| const {forceNew, targetManager, workspace} = opts; |
| if (!debuggerWorkspaceBindingInstance || forceNew) { |
| if (!targetManager || !workspace) { |
| throw new Error( |
| `Unable to create settings: targetManager and workspace must be provided: ${new Error().stack}`); |
| } |
| |
| debuggerWorkspaceBindingInstance = new DebuggerWorkspaceBinding(targetManager, workspace); |
| } |
| |
| return debuggerWorkspaceBindingInstance; |
| } |
| |
| /** |
| * @param {!DebuggerSourceMapping} sourceMapping |
| */ |
| addSourceMapping(sourceMapping) { |
| this._sourceMappings.push(sourceMapping); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| */ |
| modelAdded(debuggerModel) { |
| this._debuggerModelToData.set(debuggerModel, new ModelData(debuggerModel, this)); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| */ |
| modelRemoved(debuggerModel) { |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| modelData._dispose(); |
| this._debuggerModelToData.delete(debuggerModel); |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| * @return {?DebuggerLanguagePluginManager} |
| */ |
| getLanguagePluginManager(debuggerModel) { |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| if (!modelData) { |
| return null; |
| } |
| return modelData.pluginManager; |
| } |
| |
| /** |
| * The promise returned by this function is resolved once all *currently* |
| * pending LiveLocations are processed. |
| * |
| * @return {!Promise} |
| */ |
| pendingLiveLocationChangesPromise() { |
| return Promise.all(this._liveLocationPromises); |
| } |
| |
| /** |
| * @param {!Promise} promise |
| */ |
| _recordLiveLocationChange(promise) { |
| promise.then(() => { |
| this._liveLocationPromises.delete(promise); |
| }); |
| this._liveLocationPromises.add(promise); |
| } |
| |
| /** |
| * @param {!SDK.Script.Script} script |
| */ |
| async updateLocations(script) { |
| const modelData = this._debuggerModelToData.get(script.debuggerModel); |
| if (modelData) { |
| const updatePromise = modelData._updateLocations(script); |
| this._recordLiveLocationChange(updatePromise); |
| await updatePromise; |
| } |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| * @return {!Promise<!Location>} |
| */ |
| createLiveLocation(rawLocation, updateDelegate, locationPool) { |
| const modelData = this._debuggerModelToData.get(rawLocation.script().debuggerModel); |
| const liveLocationPromise = modelData._createLiveLocation(rawLocation, updateDelegate, locationPool); |
| this._recordLiveLocationChange(liveLocationPromise); |
| return liveLocationPromise; |
| } |
| |
| /** |
| * @param {!Array<!SDK.DebuggerModel.Location>} rawLocations |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| * @return {!Promise<!Bindings.LiveLocation>} |
| */ |
| async createStackTraceTopFrameLiveLocation(rawLocations, updateDelegate, locationPool) { |
| console.assert(rawLocations.length); |
| const locationPromise = |
| StackTraceTopFrameLocation.createStackTraceTopFrameLocation(rawLocations, this, updateDelegate, locationPool); |
| this._recordLiveLocationChange(locationPromise); |
| return locationPromise; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} location |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| * @return {!Promise<?Location>} |
| */ |
| async createCallFrameLiveLocation(location, updateDelegate, locationPool) { |
| const script = location.script(); |
| if (!script) { |
| return null; |
| } |
| const debuggerModel = location.debuggerModel; |
| const liveLocationPromise = this.createLiveLocation(location, updateDelegate, locationPool); |
| this._recordLiveLocationChange(liveLocationPromise); |
| const liveLocation = await liveLocationPromise; |
| this._registerCallFrameLiveLocation(debuggerModel, liveLocation); |
| return liveLocation; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @return {!Promise<?Workspace.UISourceCode.UILocation>} |
| */ |
| async rawLocationToUILocation(rawLocation) { |
| for (const sourceMapping of this._sourceMappings) { |
| const uiLocation = sourceMapping.rawLocationToUILocation(rawLocation); |
| if (uiLocation) { |
| return uiLocation; |
| } |
| } |
| const modelData = this._debuggerModelToData.get(rawLocation.debuggerModel); |
| return modelData ? modelData._rawLocationToUILocation(rawLocation) : null; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| * @param {string} url |
| * @param {boolean} isContentScript |
| */ |
| uiSourceCodeForSourceMapSourceURL(debuggerModel, url, isContentScript) { |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| if (!modelData) { |
| return null; |
| } |
| return modelData._compilerMapping.uiSourceCodeForURL(url, isContentScript); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Promise<!Array<!SDK.DebuggerModel.Location>>} |
| */ |
| async uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber) { |
| for (const sourceMapping of this._sourceMappings) { |
| const locations = sourceMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); |
| if (locations.length) { |
| return locations; |
| } |
| } |
| |
| const locationsPromises = []; |
| for (const modelData of this._debuggerModelToData.values()) { |
| locationsPromises.push(modelData._uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber)); |
| } |
| return (await Promise.all(locationsPromises)).flat(); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Array<!SDK.DebuggerModel.Location>} |
| */ |
| uiLocationToRawLocationsForUnformattedJavaScript(uiSourceCode, lineNumber, columnNumber) { |
| console.assert(uiSourceCode.contentType().isScript()); |
| const locations = []; |
| for (const modelData of this._debuggerModelToData.values()) { |
| locations.push(...modelData._uiLocationToRawLocationsExcludeAsync(uiSourceCode, lineNumber, columnNumber)); |
| } |
| return locations; |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UILocation} uiLocation |
| * @return {!Promise<!Workspace.UISourceCode.UILocation>} |
| */ |
| async normalizeUILocation(uiLocation) { |
| const rawLocations = |
| await this.uiLocationToRawLocations(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber); |
| for (const location of rawLocations) { |
| const uiLocationCandidate = await this.rawLocationToUILocation(location); |
| if (uiLocationCandidate) { |
| return uiLocationCandidate; |
| } |
| } |
| return uiLocation; |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| * @return {?ResourceScriptFile} |
| */ |
| scriptFile(uiSourceCode, debuggerModel) { |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| return modelData ? modelData._resourceMapping.scriptFile(uiSourceCode) : null; |
| } |
| |
| /** |
| * @param {!SDK.Script.Script} script |
| * @return {?SDK.SourceMap.SourceMap} |
| */ |
| sourceMapForScript(script) { |
| const modelData = this._debuggerModelToData.get(script.debuggerModel); |
| if (!modelData) { |
| return null; |
| } |
| return modelData._compilerMapping.sourceMapForScript(script); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _globalObjectCleared(event) { |
| const debuggerModel = /** @type {!SDK.DebuggerModel.DebuggerModel} */ (event.data); |
| this._reset(debuggerModel); |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| */ |
| _reset(debuggerModel) { |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| for (const location of modelData.callFrameLocations.values()) { |
| this._removeLiveLocation(location); |
| } |
| modelData.callFrameLocations.clear(); |
| } |
| |
| /** |
| * @param {!SDK.SDKModel.Target} target |
| */ |
| _resetForTest(target) { |
| const debuggerModel = |
| /** @type {!SDK.DebuggerModel.DebuggerModel} */ (target.model(SDK.DebuggerModel.DebuggerModel)); |
| const modelData = this._debuggerModelToData.get(debuggerModel); |
| modelData._resourceMapping.resetForTest(); |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| * @param {!Location} location |
| */ |
| _registerCallFrameLiveLocation(debuggerModel, location) { |
| const locations = this._debuggerModelToData.get(debuggerModel).callFrameLocations; |
| locations.add(location); |
| } |
| |
| /** |
| * @param {!Location} location |
| */ |
| _removeLiveLocation(location) { |
| const modelData = this._debuggerModelToData.get(location._script.debuggerModel); |
| if (modelData) { |
| modelData._disposeLocation(location); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _debuggerResumed(event) { |
| const debuggerModel = /** @type {!SDK.DebuggerModel.DebuggerModel} */ (event.data); |
| this._reset(debuggerModel); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| class ModelData { |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerModel} debuggerModel |
| * @param {!DebuggerWorkspaceBinding} debuggerWorkspaceBinding |
| */ |
| constructor(debuggerModel, debuggerWorkspaceBinding) { |
| this._debuggerModel = debuggerModel; |
| this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; |
| |
| /** @type {!Set.<!Location>} */ |
| this.callFrameLocations = new Set(); |
| |
| const workspace = debuggerWorkspaceBinding._workspace; |
| |
| if (Root.Runtime.experiments.isEnabled('wasmDWARFDebugging')) { |
| this._pluginManager = new DebuggerLanguagePluginManager(debuggerModel, workspace, debuggerWorkspaceBinding); |
| } |
| |
| |
| this._defaultMapping = new DefaultScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); |
| this._resourceMapping = new ResourceScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); |
| this._compilerMapping = new CompilerScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); |
| |
| /** @type {!Platform.Multimap<!SDK.Script.Script, !Location>} */ |
| this._locations = new Platform.Multimap(); |
| |
| debuggerModel.setBeforePausedCallback(this._beforePaused.bind(this)); |
| } |
| |
| /** |
| * return {?DebuggerLanguagePluginManager} |
| */ |
| get pluginManager() { |
| return this._pluginManager || null; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| * @return {!Promise<!Location>} |
| */ |
| async _createLiveLocation(rawLocation, updateDelegate, locationPool) { |
| const script = /** @type {!SDK.Script.Script} */ (rawLocation.script()); |
| console.assert(script); |
| const location = new Location(script, rawLocation, this._debuggerWorkspaceBinding, updateDelegate, locationPool); |
| this._locations.set(script, location); |
| await location.update(); |
| return location; |
| } |
| |
| /** |
| * @param {!Location} location |
| */ |
| _disposeLocation(location) { |
| this._locations.delete(location._script, location); |
| } |
| |
| /** |
| * @param {!SDK.Script.Script} script |
| */ |
| async _updateLocations(script) { |
| const promises = []; |
| for (const location of this._locations.get(script)) { |
| promises.push(location.update()); |
| } |
| return Promise.all(promises); |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @return {!Promise<?Workspace.UISourceCode.UILocation>} |
| */ |
| async _rawLocationToUILocation(rawLocation) { |
| let uiLocation = null; |
| if (Root.Runtime.experiments.isEnabled('wasmDWARFDebugging')) { |
| uiLocation = await this._pluginManager.rawLocationToUILocation(rawLocation); |
| } |
| uiLocation = uiLocation || this._compilerMapping.rawLocationToUILocation(rawLocation); |
| uiLocation = uiLocation || this._resourceMapping.rawLocationToUILocation(rawLocation); |
| uiLocation = uiLocation || ResourceMapping.instance().jsLocationToUILocation(rawLocation); |
| uiLocation = uiLocation || this._defaultMapping.rawLocationToUILocation(rawLocation); |
| return /** @type {!Workspace.UISourceCode.UILocation} */ (uiLocation); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Promise<!Array<!SDK.DebuggerModel.Location>>} |
| */ |
| async _uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber) { |
| let rawLocations = null; |
| if (Root.Runtime.experiments.isEnabled('wasmDWARFDebugging')) { |
| rawLocations = await this._pluginManager.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); |
| } |
| rawLocations = rawLocations || this._uiLocationToRawLocationsExcludeAsync(uiSourceCode, lineNumber, columnNumber); |
| return rawLocations; |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Array<!SDK.DebuggerModel.Location>} |
| */ |
| _uiLocationToRawLocationsExcludeAsync(uiSourceCode, lineNumber, columnNumber) { |
| let locations = this._compilerMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); |
| locations = locations.length ? |
| locations : |
| this._resourceMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); |
| locations = locations.length ? |
| locations : |
| ResourceMapping.instance().uiLocationToJSLocations(uiSourceCode, lineNumber, columnNumber); |
| locations = locations.length ? |
| locations : |
| this._defaultMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); |
| return locations; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.DebuggerPausedDetails} debuggerPausedDetails |
| * @return {boolean} |
| */ |
| _beforePaused(debuggerPausedDetails) { |
| const callFrame = debuggerPausedDetails.callFrames[0]; |
| if (!callFrame) { |
| return false; |
| } |
| if (callFrame.script.sourceMapURL !== SDK.SourceMap.WasmSourceMap.FAKE_URL && |
| !Root.Runtime.experiments.isEnabled('emptySourceMapAutoStepping')) { |
| return true; |
| } |
| return !!this._compilerMapping.mapsToSourceCode(callFrame.location()); |
| } |
| |
| _dispose() { |
| this._debuggerModel.setBeforePausedCallback(null); |
| this._compilerMapping.dispose(); |
| this._resourceMapping.dispose(); |
| this._defaultMapping.dispose(); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class Location extends LiveLocationWithPool { |
| /** |
| * @param {!SDK.Script.Script} script |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @param {!DebuggerWorkspaceBinding} binding |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| */ |
| constructor(script, rawLocation, binding, updateDelegate, locationPool) { |
| super(updateDelegate, locationPool); |
| this._script = script; |
| this._rawLocation = rawLocation; |
| this._binding = binding; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<?Workspace.UISourceCode.UILocation>} |
| */ |
| async uiLocation() { |
| const debuggerModelLocation = this._rawLocation; |
| return this._binding.rawLocationToUILocation(debuggerModelLocation); |
| } |
| |
| /** |
| * @override |
| */ |
| dispose() { |
| super.dispose(); |
| this._binding._removeLiveLocation(this); |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<boolean>} |
| */ |
| async isBlackboxed() { |
| const uiLocation = await this.uiLocation(); |
| return uiLocation ? BlackboxManager.instance().isBlackboxedUISourceCode(uiLocation.uiSourceCode) : false; |
| } |
| } |
| |
| class StackTraceTopFrameLocation extends LiveLocationWithPool { |
| /** |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| */ |
| constructor(updateDelegate, locationPool) { |
| super(updateDelegate, locationPool); |
| this._updateScheduled = true; |
| /** @type {?LiveLocation} */ |
| this._current = null; |
| /** @type {?Array<!LiveLocation>} */ |
| this._locations = null; |
| } |
| |
| /** |
| * @param {!Array<!SDK.DebuggerModel.Location>} rawLocations |
| * @param {!DebuggerWorkspaceBinding} binding |
| * @param {function(!LiveLocation)} updateDelegate |
| * @param {!LiveLocationPool} locationPool |
| * @return {!Promise<!StackTraceTopFrameLocation>} |
| */ |
| static async createStackTraceTopFrameLocation(rawLocations, binding, updateDelegate, locationPool) { |
| const location = new StackTraceTopFrameLocation(updateDelegate, locationPool); |
| const locationsPromises = rawLocations.map( |
| rawLocation => binding.createLiveLocation(rawLocation, location._scheduleUpdate.bind(location), locationPool)); |
| location._locations = await Promise.all(locationsPromises); |
| await location._updateLocation(); |
| return location; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<?Workspace.UISourceCode.UILocation>} |
| */ |
| async uiLocation() { |
| return this._current ? this._current.uiLocation() : null; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<boolean>} |
| */ |
| async isBlackboxed() { |
| return this._current ? this._current.isBlackboxed() : false; |
| } |
| |
| /** |
| * @override |
| */ |
| dispose() { |
| super.dispose(); |
| if (this._locations) { |
| for (const location of this._locations) { |
| location.dispose(); |
| } |
| } |
| this._locations = null; |
| this._current = null; |
| } |
| |
| _scheduleUpdate() { |
| if (this._updateScheduled) { |
| return; |
| } |
| this._updateScheduled = true; |
| setImmediate(this._updateLocation.bind(this)); |
| } |
| |
| async _updateLocation() { |
| this._updateScheduled = false; |
| if (!this._locations || this._locations.length === 0) { |
| return; |
| } |
| |
| this._current = this._locations[0]; |
| for (const location of this._locations) { |
| if (!(await location.isBlackboxed())) { |
| this._current = location; |
| break; |
| } |
| } |
| this.update(); |
| } |
| } |
| |
| /** |
| * @interface |
| */ |
| export class DebuggerSourceMapping { |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @return {?Workspace.UISourceCode.UILocation} |
| */ |
| rawLocationToUILocation(rawLocation) { |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!Array<!SDK.DebuggerModel.Location>} |
| */ |
| uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber) { |
| } |
| } |