UNPKG

chrome-devtools-frontend

Version:
742 lines (659 loc) • 30.7 kB
// 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. import * as Common from '../../core/common/common.js'; import * as Platform from '../../core/platform/platform.js'; import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import type * as TextUtils from '../text_utils/text_utils.js'; import * as Workspace from '../workspace/workspace.js'; import * as Protocol from '../../generated/protocol.js'; import {CompilerScriptMapping} from './CompilerScriptMapping.js'; import {DebuggerLanguagePluginManager} from './DebuggerLanguagePlugins.js'; import {DefaultScriptMapping} from './DefaultScriptMapping.js'; import {IgnoreListManager} from './IgnoreListManager.js'; import {LiveLocationWithPool, type LiveLocation, type LiveLocationPool} from './LiveLocation.js'; import {NetworkProject} from './NetworkProject.js'; import {type ResourceMapping} from './ResourceMapping.js'; import {ResourceScriptMapping, type ResourceScriptFile} from './ResourceScriptMapping.js'; let debuggerWorkspaceBindingInstance: DebuggerWorkspaceBinding|undefined; export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> { readonly resourceMapping: ResourceMapping; readonly #sourceMappings: DebuggerSourceMapping[]; readonly #debuggerModelToData: Map<SDK.DebuggerModel.DebuggerModel, ModelData>; readonly #liveLocationPromises: Set<Promise<void|Location|StackTraceTopFrameLocation|null>>; pluginManager: DebuggerLanguagePluginManager|null; #targetManager: SDK.TargetManager.TargetManager; private constructor(resourceMapping: ResourceMapping, targetManager: SDK.TargetManager.TargetManager) { this.resourceMapping = resourceMapping; this.#sourceMappings = []; 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); this.#targetManager = targetManager; this.#liveLocationPromises = new Set(); this.pluginManager = Root.Runtime.experiments.isEnabled('wasmDWARFDebugging') ? new DebuggerLanguagePluginManager(targetManager, resourceMapping.workspace, this) : null; } initPluginManagerForTest(): DebuggerLanguagePluginManager|null { if (Root.Runtime.experiments.isEnabled('wasmDWARFDebugging')) { if (!this.pluginManager) { this.pluginManager = new DebuggerLanguagePluginManager(this.#targetManager, this.resourceMapping.workspace, this); } } else { this.pluginManager = null; } return this.pluginManager; } static instance(opts: { forceNew: boolean|null, resourceMapping: ResourceMapping|null, targetManager: SDK.TargetManager.TargetManager|null, } = {forceNew: null, resourceMapping: null, targetManager: null}): DebuggerWorkspaceBinding { const {forceNew, resourceMapping, targetManager} = opts; if (!debuggerWorkspaceBindingInstance || forceNew) { if (!resourceMapping || !targetManager) { throw new Error( `Unable to create DebuggerWorkspaceBinding: resourceMapping and targetManager must be provided: ${ new Error().stack}`); } debuggerWorkspaceBindingInstance = new DebuggerWorkspaceBinding(resourceMapping, targetManager); } return debuggerWorkspaceBindingInstance; } static removeInstance(): void { debuggerWorkspaceBindingInstance = undefined; } addSourceMapping(sourceMapping: DebuggerSourceMapping): void { this.#sourceMappings.push(sourceMapping); } removeSourceMapping(sourceMapping: DebuggerSourceMapping): void { const index = this.#sourceMappings.indexOf(sourceMapping); if (index !== -1) { this.#sourceMappings.splice(index, 1); } } private async computeAutoStepRanges(mode: SDK.DebuggerModel.StepMode, callFrame: SDK.DebuggerModel.CallFrame): Promise<SDK.DebuggerModel.LocationRange[]> { function contained(location: SDK.DebuggerModel.Location, range: SDK.DebuggerModel.LocationRange): boolean { const {start, end} = range; if (start.scriptId !== location.scriptId) { return false; } if (location.lineNumber < start.lineNumber || location.lineNumber > end.lineNumber) { return false; } if (location.lineNumber === start.lineNumber && location.columnNumber < start.columnNumber) { return false; } if (location.lineNumber === end.lineNumber && location.columnNumber >= end.columnNumber) { return false; } return true; } const rawLocation = callFrame.location(); if (!rawLocation) { return []; } const pluginManager = this.pluginManager; let ranges: SDK.DebuggerModel.LocationRange[] = []; if (pluginManager) { if (mode === SDK.DebuggerModel.StepMode.StepOut) { // Step out of inline function. return await pluginManager.getInlinedFunctionRanges(rawLocation); } const uiLocation = await pluginManager.rawLocationToUILocation(rawLocation); if (uiLocation) { ranges = await pluginManager.uiLocationToRawLocationRanges( uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber) || []; // TODO(bmeurer): Remove the {rawLocation} from the {ranges}? ranges = ranges.filter(range => contained(rawLocation, range)); if (mode === SDK.DebuggerModel.StepMode.StepOver) { // Step over an inlined function. ranges = ranges.concat(await pluginManager.getInlinedCalleesRanges(rawLocation)); } return ranges; } } const compilerMapping = this.#debuggerModelToData.get(rawLocation.debuggerModel)?.compilerMapping; if (!compilerMapping) { return []; } if (mode === SDK.DebuggerModel.StepMode.StepOut) { // We should actually return the source range for the entire function // to skip over. Since we don't have that, we return an empty range // instead, to signal that we should perform a regular step-out. return []; } ranges = compilerMapping.getLocationRangesForSameSourceLocation(rawLocation); ranges = ranges.filter(range => contained(rawLocation, range)); return ranges; } modelAdded(debuggerModel: SDK.DebuggerModel.DebuggerModel): void { debuggerModel.setBeforePausedCallback(this.shouldPause.bind(this)); this.#debuggerModelToData.set(debuggerModel, new ModelData(debuggerModel, this)); debuggerModel.setComputeAutoStepRangesCallback(this.computeAutoStepRanges.bind(this)); } modelRemoved(debuggerModel: SDK.DebuggerModel.DebuggerModel): void { debuggerModel.setComputeAutoStepRangesCallback(null); const modelData = this.#debuggerModelToData.get(debuggerModel); if (modelData) { modelData.dispose(); this.#debuggerModelToData.delete(debuggerModel); } } /** * The promise returned by this function is resolved once all *currently* * pending LiveLocations are processed. */ async pendingLiveLocationChangesPromise(): Promise<void|Location|StackTraceTopFrameLocation|null> { await Promise.all(this.#liveLocationPromises); } private recordLiveLocationChange(promise: Promise<void|Location|StackTraceTopFrameLocation|null>): void { void promise.then(() => { this.#liveLocationPromises.delete(promise); }); this.#liveLocationPromises.add(promise); } async updateLocations(script: SDK.Script.Script): Promise<void> { const modelData = this.#debuggerModelToData.get(script.debuggerModel); if (modelData) { const updatePromise = modelData.updateLocations(script); this.recordLiveLocationChange(updatePromise); await updatePromise; } } async createLiveLocation( rawLocation: SDK.DebuggerModel.Location, updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool): Promise<Location|null> { const modelData = this.#debuggerModelToData.get(rawLocation.debuggerModel); if (!modelData) { return null; } const liveLocationPromise = modelData.createLiveLocation(rawLocation, updateDelegate, locationPool); this.recordLiveLocationChange(liveLocationPromise); return liveLocationPromise; } async createStackTraceTopFrameLiveLocation( rawLocations: SDK.DebuggerModel.Location[], updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool): Promise<LiveLocation> { console.assert(rawLocations.length > 0); const locationPromise = StackTraceTopFrameLocation.createStackTraceTopFrameLocation(rawLocations, this, updateDelegate, locationPool); this.recordLiveLocationChange(locationPromise); return locationPromise; } async createCallFrameLiveLocation( location: SDK.DebuggerModel.Location, updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool): Promise<Location|null> { 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; if (!liveLocation) { return null; } this.registerCallFrameLiveLocation(debuggerModel, liveLocation); return liveLocation; } async rawLocationToUILocation(rawLocation: SDK.DebuggerModel.Location): Promise<Workspace.UISourceCode.UILocation|null> { for (const sourceMapping of this.#sourceMappings) { const uiLocation = sourceMapping.rawLocationToUILocation(rawLocation); if (uiLocation) { return uiLocation; } } if (this.pluginManager) { const uiLocation = await this.pluginManager.rawLocationToUILocation(rawLocation); if (uiLocation) { return uiLocation; } } const modelData = this.#debuggerModelToData.get(rawLocation.debuggerModel); return modelData ? modelData.rawLocationToUILocation(rawLocation) : null; } uiSourceCodeForSourceMapSourceURL( debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString, isContentScript: boolean): Workspace.UISourceCode.UISourceCode|null { const modelData = this.#debuggerModelToData.get(debuggerModel); if (!modelData) { return null; } return modelData.compilerMapping.uiSourceCodeForURL(url, isContentScript); } async uiSourceCodeForSourceMapSourceURLPromise( debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString, isContentScript: boolean): Promise<Workspace.UISourceCode.UISourceCode> { const uiSourceCode = this.uiSourceCodeForSourceMapSourceURL(debuggerModel, url, isContentScript); return uiSourceCode || this.waitForUISourceCodeAdded(url, debuggerModel.target()); } async uiSourceCodeForDebuggerLanguagePluginSourceURLPromise( debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString): Promise<Workspace.UISourceCode.UISourceCode|null> { if (this.pluginManager) { const uiSourceCode = this.pluginManager.uiSourceCodeForURL(debuggerModel, url); return uiSourceCode || this.waitForUISourceCodeAdded(url, debuggerModel.target()); } return null; } uiSourceCodeForScript(script: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|null { const modelData = this.#debuggerModelToData.get(script.debuggerModel); if (!modelData) { return null; } return modelData.uiSourceCodeForScript(script); } waitForUISourceCodeAdded(url: Platform.DevToolsPath.UrlString, target: SDK.Target.Target): Promise<Workspace.UISourceCode.UISourceCode> { return new Promise(resolve => { const workspace = Workspace.Workspace.WorkspaceImpl.instance(); const descriptor = workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, event => { const uiSourceCode = event.data; if (uiSourceCode.url() === url && NetworkProject.targetForUISourceCode(uiSourceCode) === target) { workspace.removeEventListener(Workspace.Workspace.Events.UISourceCodeAdded, descriptor.listener); resolve(uiSourceCode); } }); }); } async uiLocationToRawLocations( uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber?: number): Promise<SDK.DebuggerModel.Location[]> { for (const sourceMapping of this.#sourceMappings) { const locations = sourceMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); if (locations.length) { return locations; } } const locations = await this.pluginManager?.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); if (locations) { return locations; } for (const modelData of this.#debuggerModelToData.values()) { const locations = modelData.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); if (locations.length) { return locations; } } return []; } /** * Computes all the raw location ranges that intersect with the {@link textRange} in the given * {@link uiSourceCode}. The reverse mappings of the returned ranges must not be fully contained * with the {@link textRange} and it's the responsibility of the caller to appropriately filter or * clamp if desired. * * It's important to note that for a contiguous range in the {@link uiSourceCode} there can be a * variety of non-contiguous raw location ranges that intersect with the {@link textRange}. A * simple example is that of an HTML document with multiple inline `<script>`s in the same line, * so just asking for the raw locations in this single line will return a set of location ranges * in different scripts. * * This method returns an empty array if this {@link uiSourceCode} is not provided by any of the * mappings for this instance. * * @param uiSourceCode the {@link UISourceCode} to which the {@link textRange} belongs. * @param textRange the text range in terms of the UI. * @returns the list of raw location ranges that intersect with the text range or `[]` if * the {@link uiSourceCode} does not belong to this instance. */ async uiLocationRangeToRawLocationRanges( uiSourceCode: Workspace.UISourceCode.UISourceCode, textRange: TextUtils.TextRange.TextRange): Promise<SDK.DebuggerModel.LocationRange[]> { for (const sourceMapping of this.#sourceMappings) { const ranges = sourceMapping.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); if (ranges) { return ranges; } } if (this.pluginManager !== null) { const ranges = await this.pluginManager.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); if (ranges) { return ranges; } } for (const modelData of this.#debuggerModelToData.values()) { const ranges = modelData.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); if (ranges) { return ranges; } } return []; } uiLocationToRawLocationsForUnformattedJavaScript( uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber: number): SDK.DebuggerModel.Location[] { console.assert(uiSourceCode.contentType().isScript()); const locations = []; for (const modelData of this.#debuggerModelToData.values()) { locations.push(...modelData.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber)); } return locations; } async normalizeUILocation(uiLocation: Workspace.UISourceCode.UILocation): Promise<Workspace.UISourceCode.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; } /** * Computes the set of lines in the {@link uiSourceCode} that map to scripts by either looking at * the debug info (if any) or checking for inline scripts within documents. If this set cannot be * computed or all the lines in the {@link uiSourceCode} correspond to lines in a script, `null` * is returned here. * * @param uiSourceCode the source entity. * @returns a set of known mapped lines for {@link uiSourceCode} or `null` if it's impossible to * determine the set or the {@link uiSourceCode} does not map to or include any scripts. */ async getMappedLines(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<Set<number>|null> { for (const modelData of this.#debuggerModelToData.values()) { const mappedLines = modelData.getMappedLines(uiSourceCode); if (mappedLines !== null) { return mappedLines; } } const {pluginManager} = this; if (!pluginManager) { return null; } return await pluginManager.getMappedLines(uiSourceCode); } scriptFile(uiSourceCode: Workspace.UISourceCode.UISourceCode, debuggerModel: SDK.DebuggerModel.DebuggerModel): ResourceScriptFile|null { const modelData = this.#debuggerModelToData.get(debuggerModel); return modelData ? modelData.getResourceScriptMapping().scriptFile(uiSourceCode) : null; } scriptsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): SDK.Script.Script[] { const scripts = new Set<SDK.Script.Script>(); if (this.pluginManager) { this.pluginManager.scriptsForUISourceCode(uiSourceCode).forEach(script => scripts.add(script)); } for (const modelData of this.#debuggerModelToData.values()) { const resourceScriptFile = modelData.getResourceScriptMapping().scriptFile(uiSourceCode); if (resourceScriptFile && resourceScriptFile.script) { scripts.add(resourceScriptFile.script); } modelData.compilerMapping.scriptsForUISourceCode(uiSourceCode).forEach(script => scripts.add(script)); } return [...scripts]; } supportsConditionalBreakpoints(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean { // DevTools traditionally supported (JavaScript) conditions // for breakpoints everywhere, so we keep that behavior... if (!this.pluginManager) { return true; } const scripts = this.pluginManager.scriptsForUISourceCode(uiSourceCode); return scripts.every(script => script.isJavaScript()); } private globalObjectCleared(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): void { this.reset(event.data); } private reset(debuggerModel: SDK.DebuggerModel.DebuggerModel): void { const modelData = this.#debuggerModelToData.get(debuggerModel); if (!modelData) { return; } for (const location of modelData.callFrameLocations.values()) { this.removeLiveLocation(location); } modelData.callFrameLocations.clear(); } resetForTest(target: SDK.Target.Target): void { const debuggerModel = (target.model(SDK.DebuggerModel.DebuggerModel) as SDK.DebuggerModel.DebuggerModel); const modelData = this.#debuggerModelToData.get(debuggerModel); if (modelData) { modelData.getResourceScriptMapping().resetForTest(); } } private registerCallFrameLiveLocation(debuggerModel: SDK.DebuggerModel.DebuggerModel, location: Location): void { const modelData = this.#debuggerModelToData.get(debuggerModel); if (modelData) { const locations = modelData.callFrameLocations; locations.add(location); } } removeLiveLocation(location: Location): void { const modelData = this.#debuggerModelToData.get(location.rawLocation.debuggerModel); if (modelData) { modelData.disposeLocation(location); } } private debuggerResumed(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): void { this.reset(event.data); } private async shouldPause( debuggerPausedDetails: SDK.DebuggerModel.DebuggerPausedDetails, autoSteppingContext: SDK.DebuggerModel.Location|null): Promise<boolean> { // This function returns false if the debugger should continue stepping const {callFrames: [frame]} = debuggerPausedDetails; if (!frame) { return false; } const functionLocation = frame.functionLocation(); if (!autoSteppingContext || debuggerPausedDetails.reason !== Protocol.Debugger.PausedEventReason.Step || !functionLocation || !this.pluginManager || !frame.script.isWasm() || !Common.Settings.moduleSetting('wasmAutoStepping').get() || !this.pluginManager.hasPluginForScript(frame.script)) { return true; } const uiLocation = await this.pluginManager.rawLocationToUILocation(frame.location()); if (uiLocation) { return true; } return autoSteppingContext.script() !== functionLocation.script() || autoSteppingContext.columnNumber !== functionLocation.columnNumber || autoSteppingContext.lineNumber !== functionLocation.lineNumber; } } class ModelData { readonly #debuggerModel: SDK.DebuggerModel.DebuggerModel; readonly #debuggerWorkspaceBinding: DebuggerWorkspaceBinding; callFrameLocations: Set<Location>; #defaultMapping: DefaultScriptMapping; readonly #resourceMapping: ResourceMapping; #resourceScriptMapping: ResourceScriptMapping; readonly compilerMapping: CompilerScriptMapping; readonly #locations: Platform.MapUtilities.Multimap<string, Location>; constructor(debuggerModel: SDK.DebuggerModel.DebuggerModel, debuggerWorkspaceBinding: DebuggerWorkspaceBinding) { this.#debuggerModel = debuggerModel; this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding; this.callFrameLocations = new Set(); const {workspace} = debuggerWorkspaceBinding.resourceMapping; this.#defaultMapping = new DefaultScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); this.#resourceMapping = debuggerWorkspaceBinding.resourceMapping; this.#resourceScriptMapping = new ResourceScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); this.compilerMapping = new CompilerScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); this.#locations = new Platform.MapUtilities.Multimap(); } async createLiveLocation( rawLocation: SDK.DebuggerModel.Location, updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool): Promise<Location> { console.assert(rawLocation.scriptId !== ''); const scriptId = rawLocation.scriptId; const location = new Location(scriptId, rawLocation, this.#debuggerWorkspaceBinding, updateDelegate, locationPool); this.#locations.set(scriptId, location); await location.update(); return location; } disposeLocation(location: Location): void { this.#locations.delete(location.scriptId, location); } async updateLocations(script: SDK.Script.Script): Promise<void> { const promises = []; for (const location of this.#locations.get(script.scriptId)) { promises.push(location.update()); } await Promise.all(promises); } rawLocationToUILocation(rawLocation: SDK.DebuggerModel.Location): Workspace.UISourceCode.UILocation|null { let uiLocation = this.compilerMapping.rawLocationToUILocation(rawLocation); uiLocation = uiLocation || this.#resourceScriptMapping.rawLocationToUILocation(rawLocation); uiLocation = uiLocation || this.#resourceMapping.jsLocationToUILocation(rawLocation); uiLocation = uiLocation || this.#defaultMapping.rawLocationToUILocation(rawLocation); return uiLocation; } uiSourceCodeForScript(script: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|null { let uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null; uiSourceCode = uiSourceCode || this.#resourceScriptMapping.uiSourceCodeForScript(script); uiSourceCode = uiSourceCode || this.#resourceMapping.uiSourceCodeForScript(script); uiSourceCode = uiSourceCode || this.#defaultMapping.uiSourceCodeForScript(script); return uiSourceCode; } uiLocationToRawLocations( uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber: number|undefined = 0): SDK.DebuggerModel.Location[] { // TODO(crbug.com/1153123): Revisit the `#columnNumber = 0` and also preserve `undefined` for source maps? let locations = this.compilerMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); locations = locations.length ? locations : this.#resourceScriptMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); locations = locations.length ? locations : this.#resourceMapping.uiLocationToJSLocations(uiSourceCode, lineNumber, columnNumber); locations = locations.length ? locations : this.#defaultMapping.uiLocationToRawLocations(uiSourceCode, lineNumber, columnNumber); return locations; } uiLocationRangeToRawLocationRanges( uiSourceCode: Workspace.UISourceCode.UISourceCode, textRange: TextUtils.TextRange.TextRange): SDK.DebuggerModel.LocationRange[]|null { let ranges = this.compilerMapping.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); ranges ??= this.#resourceScriptMapping.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); ranges ??= this.#resourceMapping.uiLocationRangeToJSLocationRanges(uiSourceCode, textRange); ranges ??= this.#defaultMapping.uiLocationRangeToRawLocationRanges(uiSourceCode, textRange); return ranges; } getMappedLines(uiSourceCode: Workspace.UISourceCode.UISourceCode): Set<number>|null { const mappedLines = this.compilerMapping.getMappedLines(uiSourceCode); // TODO(crbug.com/1411431): The scripts from the ResourceMapping appear over time, // and there's currently no way to inform the UI to update. // mappedLines = mappedLines ?? this.#resourceMapping.getMappedLines(uiSourceCode); return mappedLines; } dispose(): void { this.#debuggerModel.setBeforePausedCallback(null); this.compilerMapping.dispose(); this.#resourceScriptMapping.dispose(); this.#defaultMapping.dispose(); } getResourceScriptMapping(): ResourceScriptMapping { return this.#resourceScriptMapping; } } export class Location extends LiveLocationWithPool { readonly scriptId: string; readonly rawLocation: SDK.DebuggerModel.Location; readonly #binding: DebuggerWorkspaceBinding; constructor( scriptId: string, rawLocation: SDK.DebuggerModel.Location, binding: DebuggerWorkspaceBinding, updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool) { super(updateDelegate, locationPool); this.scriptId = scriptId; this.rawLocation = rawLocation; this.#binding = binding; } override async uiLocation(): Promise<Workspace.UISourceCode.UILocation|null> { const debuggerModelLocation = this.rawLocation; return this.#binding.rawLocationToUILocation(debuggerModelLocation); } override dispose(): void { super.dispose(); this.#binding.removeLiveLocation(this); } override async isIgnoreListed(): Promise<boolean> { const uiLocation = await this.uiLocation(); if (!uiLocation) { return false; } return IgnoreListManager.instance().isUserOrSourceMapIgnoreListedUISourceCode(uiLocation.uiSourceCode); } } class StackTraceTopFrameLocation extends LiveLocationWithPool { #updateScheduled: boolean; #current: LiveLocation|null; #locations: LiveLocation[]|null; constructor(updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool) { super(updateDelegate, locationPool); this.#updateScheduled = true; this.#current = null; this.#locations = null; } static async createStackTraceTopFrameLocation( rawLocations: SDK.DebuggerModel.Location[], binding: DebuggerWorkspaceBinding, updateDelegate: (arg0: LiveLocation) => Promise<void>, locationPool: LiveLocationPool): Promise<StackTraceTopFrameLocation> { 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)).filter(l => Boolean(l)) as Location[]); await location.updateLocation(); return location; } override async uiLocation(): Promise<Workspace.UISourceCode.UILocation|null> { return this.#current ? this.#current.uiLocation() : null; } override async isIgnoreListed(): Promise<boolean> { return this.#current ? this.#current.isIgnoreListed() : false; } override dispose(): void { super.dispose(); if (this.#locations) { for (const location of this.#locations) { location.dispose(); } } this.#locations = null; this.#current = null; } private async scheduleUpdate(): Promise<void> { if (this.#updateScheduled) { return; } this.#updateScheduled = true; queueMicrotask(() => { void this.updateLocation(); }); } private async updateLocation(): Promise<void> { 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.isIgnoreListed())) { this.#current = location; break; } } void this.update(); } } export interface DebuggerSourceMapping { rawLocationToUILocation(rawLocation: SDK.DebuggerModel.Location): Workspace.UISourceCode.UILocation|null; uiLocationToRawLocations( uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber?: number): SDK.DebuggerModel.Location[]; uiLocationRangeToRawLocationRanges( uiSourceCode: Workspace.UISourceCode.UISourceCode, textRange: TextUtils.TextRange.TextRange): SDK.DebuggerModel.LocationRange[]|null; }