| // Copyright 2012 The Chromium Authors |
| // 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 SDK from '../../core/sdk/sdk.js'; |
| // eslint-disable-next-line @devtools/es-modules-import |
| import * as StackTraceImpl from '../stack_trace/stack_trace_impl.js'; |
| import * as TextUtils from '../text_utils/text_utils.js'; |
| import * as Workspace from '../workspace/workspace.js'; |
| |
| import {ContentProviderBasedProject} from './ContentProviderBasedProject.js'; |
| import type {DebuggerSourceMapping, DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js'; |
| import {NetworkProject} from './NetworkProject.js'; |
| |
| /** |
| * The `CompilerScriptMapping` maps script entities from source maps to scripts and vice versa. |
| * It is part of the {@link DebuggerWorkspaceBinding} and operates on top of the |
| * {@link SDK.SourceMapManager.SourceMapManager}. |
| * |
| * The `CompilerScriptMapping` maintains a list of {@link ContentProviderBasedProject}s, in which it |
| * creates the `UISourceCode`s for the source-mapped entities. The mapping is implemented in various |
| * layers: |
| * |
| * - `#sourceMapToProject` holds a mapping of all the attached `SourceMap`s to their respective |
| * `ContentBasedProviderBasedProject`s. When resolving raw to UI locations this is the first |
| * place to check. |
| * - `#uiSourceCodeToSourceMaps` maps every `UISourceCode` created herein to the `SourceMap` that |
| * it originated from. This is the authoritative source of information: The `#projects` might |
| * contain `UISourceCode` objects that were created from this `CompilerScriptMapping`, but which |
| * have become stale, and `#uiSourceCodeToSourceMaps` represents these as having no source maps. |
| * |
| * @see {@link SDK.SourceMap.SourceMap} |
| * @see {@link SDK.SourceMapManager.SourceMapManager} |
| */ |
| export class CompilerScriptMapping implements DebuggerSourceMapping { |
| readonly #sourceMapManager: SDK.SourceMapManager.SourceMapManager<SDK.Script.Script>; |
| readonly #debuggerWorkspaceBinding: DebuggerWorkspaceBinding; |
| readonly #stubUISourceCodes = new Map<SDK.Script.Script, Workspace.UISourceCode.UISourceCode>(); |
| readonly #stubProject: ContentProviderBasedProject; |
| readonly #eventListeners: Common.EventTarget.EventDescriptor[]; |
| readonly #projects = new Map<string, ContentProviderBasedProject>(); |
| readonly #sourceMapToProject = new Map<SDK.SourceMap.SourceMap, ContentProviderBasedProject>(); |
| readonly #uiSourceCodeToSourceMaps = |
| new Platform.MapUtilities.Multimap<Workspace.UISourceCode.UISourceCode, SDK.SourceMap.SourceMap>(); |
| readonly #debuggerModel: SDK.DebuggerModel.DebuggerModel; |
| readonly #ignoreListManager: Workspace.IgnoreListManager.IgnoreListManager; |
| |
| constructor( |
| debuggerModel: SDK.DebuggerModel.DebuggerModel, workspace: Workspace.Workspace.WorkspaceImpl, |
| debuggerWorkspaceBinding: DebuggerWorkspaceBinding) { |
| this.#sourceMapManager = debuggerModel.sourceMapManager(); |
| this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding; |
| this.#debuggerModel = debuggerModel; |
| this.#ignoreListManager = debuggerWorkspaceBinding.ignoreListManager; |
| |
| this.#stubProject = new ContentProviderBasedProject( |
| workspace, 'jsSourceMaps:stub:' + debuggerModel.target().id(), Workspace.Workspace.projectTypes.Service, '', |
| true /* isServiceProject */); |
| this.#eventListeners = [ |
| this.#sourceMapManager.addEventListener( |
| SDK.SourceMapManager.Events.SourceMapWillAttach, this.sourceMapWillAttach, this), |
| this.#sourceMapManager.addEventListener( |
| SDK.SourceMapManager.Events.SourceMapFailedToAttach, this.sourceMapFailedToAttach, this), |
| this.#sourceMapManager.addEventListener( |
| SDK.SourceMapManager.Events.SourceMapAttached, this.sourceMapAttached, this), |
| this.#sourceMapManager.addEventListener( |
| SDK.SourceMapManager.Events.SourceMapDetached, this.sourceMapDetached, this), |
| ]; |
| } |
| |
| setFunctionRanges( |
| uiSourceCode: Workspace.UISourceCode.UISourceCode, |
| ranges: SDK.SourceMapFunctionRanges.NamedFunctionRange[]): void { |
| for (const sourceMap of this.#uiSourceCodeToSourceMaps.get(uiSourceCode)) { |
| sourceMap.augmentWithScopes(uiSourceCode.url(), ranges); |
| } |
| } |
| |
| private addStubUISourceCode(script: SDK.Script.Script): void { |
| const stubUISourceCode = this.#stubProject.addContentProvider( |
| Common.ParsedURL.ParsedURL.concatenate(script.sourceURL, ':sourcemap'), |
| TextUtils.StaticContentProvider.StaticContentProvider.fromString( |
| script.sourceURL, Common.ResourceType.resourceTypes.Script, |
| '\n\n\n\n\n// Please wait a bit.\n// Compiled script is not shown while source map is being loaded!'), |
| 'text/javascript'); |
| this.#stubUISourceCodes.set(script, stubUISourceCode); |
| } |
| |
| private removeStubUISourceCode(script: SDK.Script.Script): void { |
| const uiSourceCode = this.#stubUISourceCodes.get(script); |
| this.#stubUISourceCodes.delete(script); |
| if (uiSourceCode) { |
| this.#stubProject.removeUISourceCode(uiSourceCode.url()); |
| } |
| } |
| |
| getLocationRangesForSameSourceLocation(rawLocation: SDK.DebuggerModel.Location): SDK.DebuggerModel.LocationRange[] { |
| const debuggerModel = rawLocation.debuggerModel; |
| const script = rawLocation.script(); |
| if (!script) { |
| return []; |
| } |
| const sourceMap = this.#sourceMapManager.sourceMapForClient(script); |
| if (!sourceMap) { |
| return []; |
| } |
| |
| // Find the source location for the raw location. |
| const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(rawLocation); |
| const entry = sourceMap.findEntry(lineNumber, columnNumber); |
| if (!entry?.sourceURL) { |
| return []; |
| } |
| |
| const project = this.#sourceMapToProject.get(sourceMap); |
| if (!project) { |
| return []; |
| } |
| |
| const uiSourceCode = project.uiSourceCodeForURL(entry.sourceURL); |
| if (!uiSourceCode) { |
| return []; |
| } |
| |
| // Check that the reverse is also true and we have a binding for this |uiSourceCode| |
| // that is referred to from the |sourceMap| of the |script|. |
| if (!this.#uiSourceCodeToSourceMaps.hasValue(uiSourceCode, sourceMap)) { |
| return []; |
| } |
| |
| // Map the source location back to raw location ranges. |
| const ranges = sourceMap.findReverseRanges(entry.sourceURL, entry.sourceLineNumber, entry.sourceColumnNumber); |
| return ranges.map(({startLine, startColumn, endLine, endColumn}) => { |
| const start = script.relativeLocationToRawLocation({lineNumber: startLine, columnNumber: startColumn}); |
| const end = script.relativeLocationToRawLocation({lineNumber: endLine, columnNumber: endColumn}); |
| return { |
| start: debuggerModel.createRawLocation(script, start.lineNumber, start.columnNumber), |
| end: debuggerModel.createRawLocation(script, end.lineNumber, end.columnNumber), |
| }; |
| }); |
| } |
| |
| uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString, isContentScript: boolean): |
| Workspace.UISourceCode.UISourceCode|null { |
| const projectType = |
| isContentScript ? Workspace.Workspace.projectTypes.ContentScripts : Workspace.Workspace.projectTypes.Network; |
| for (const project of this.#projects.values()) { |
| if (project.type() !== projectType) { |
| continue; |
| } |
| const uiSourceCode = project.uiSourceCodeForURL(url); |
| if (uiSourceCode) { |
| return uiSourceCode; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Resolves the source-mapped entity mapped from the given `rawLocation` if any. If the `rawLocation` |
| * does not point into a script with a source map, `null` is returned from here, while if the source |
| * map for the script is currently being loaded, a stub `UISourceCode` is returned meanwhile. Otherwise, |
| * if the script has a source map entry for the position identified by the `rawLocation`, this method |
| * will compute the location in the source-mapped file by a reverse lookup on the source map. |
| * |
| * If `rawLocation` points to a script with a source map managed by this `CompilerScriptMapping`, which |
| * is stale (because it was overridden by a more recent mapping), `null` will be returned. |
| * |
| * @param rawLocation script location. |
| * @returns the {@link Workspace.UISourceCode.UILocation} for the `rawLocation` if any. |
| */ |
| rawLocationToUILocation(rawLocation: SDK.DebuggerModel.Location): Workspace.UISourceCode.UILocation|null { |
| const script = rawLocation.script(); |
| if (!script) { |
| return null; |
| } |
| |
| const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(rawLocation); |
| const stubUISourceCode = this.#stubUISourceCodes.get(script); |
| if (stubUISourceCode) { |
| return new Workspace.UISourceCode.UILocation(stubUISourceCode, lineNumber, columnNumber); |
| } |
| |
| const sourceMap = this.#sourceMapManager.sourceMapForClient(script); |
| if (!sourceMap) { |
| return null; |
| } |
| |
| const project = this.#sourceMapToProject.get(sourceMap); |
| if (!project) { |
| return null; |
| } |
| |
| const entry = sourceMap.findEntry(lineNumber, columnNumber, rawLocation.inlineFrameIndex); |
| if (!entry?.sourceURL) { |
| return null; |
| } |
| |
| const uiSourceCode = project.uiSourceCodeForURL(entry.sourceURL); |
| if (!uiSourceCode) { |
| return null; |
| } |
| |
| // Check that the reverse is also true and we have a binding for this `uiSourceCode` |
| // that is referred to from the `sourceMap` of the `script`. |
| if (!this.#uiSourceCodeToSourceMaps.hasValue(uiSourceCode, sourceMap)) { |
| return null; |
| } |
| |
| return uiSourceCode.uiLocation(entry.sourceLineNumber, entry.sourceColumnNumber); |
| } |
| |
| /** |
| * Resolves a location within a source mapped entity managed by the `CompilerScriptMapping` |
| * to its script locations. If the `uiSourceCode` does not belong to this mapping or if its |
| * mapping is stale, an empty list will be returned. |
| * |
| * A single UI location can map to multiple different {@link SDK.DebuggerModel.RawLocation}s, |
| * and these raw locations don't even need to belong to the same script (e.g. multiple bundles |
| * can reference the same shared source file in case of code splitting, and locations within |
| * this shared source file will then resolve to locations in all the bundles). |
| * |
| * @param uiSourceCode the source mapped entity. |
| * @param lineNumber the line number in terms of the {@link uiSourceCode}. |
| * @param columnNumber the column number in terms of the {@link uiSourceCode}. |
| * @returns a list of raw locations that correspond to the UI location. |
| */ |
| uiLocationToRawLocations(uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber: number): |
| SDK.DebuggerModel.Location[] { |
| const locations = []; |
| for (const sourceMap of this.#uiSourceCodeToSourceMaps.get(uiSourceCode)) { |
| const entry = sourceMap.sourceLineMapping(uiSourceCode.url(), lineNumber, columnNumber); |
| if (!entry) { |
| continue; |
| } |
| const script = this.#sourceMapManager.clientForSourceMap(sourceMap); |
| if (!script) { |
| continue; |
| } |
| const location = script.relativeLocationToRawLocation(entry); |
| locations.push(script.debuggerModel.createRawLocation(script, location.lineNumber, location.columnNumber)); |
| } |
| return locations; |
| } |
| |
| uiLocationRangeToRawLocationRanges( |
| uiSourceCode: Workspace.UISourceCode.UISourceCode, |
| textRange: TextUtils.TextRange.TextRange): SDK.DebuggerModel.LocationRange[]|null { |
| if (!this.#uiSourceCodeToSourceMaps.has(uiSourceCode)) { |
| return null; |
| } |
| const ranges = []; |
| for (const sourceMap of this.#uiSourceCodeToSourceMaps.get(uiSourceCode)) { |
| const script = this.#sourceMapManager.clientForSourceMap(sourceMap); |
| if (!script) { |
| continue; |
| } |
| |
| for (const scriptTextRange of sourceMap.reverseMapTextRanges(uiSourceCode.url(), textRange)) { |
| const startLocation = script.relativeLocationToRawLocation(scriptTextRange.start); |
| const endLocation = script.relativeLocationToRawLocation(scriptTextRange.end); |
| const start = |
| script.debuggerModel.createRawLocation(script, startLocation.lineNumber, startLocation.columnNumber); |
| const end = script.debuggerModel.createRawLocation(script, endLocation.lineNumber, endLocation.columnNumber); |
| ranges.push({start, end}); |
| } |
| } |
| return ranges; |
| } |
| |
| async functionBoundsAtRawLocation(rawLocation: SDK.DebuggerModel.Location): |
| Promise<Workspace.UISourceCode.UIFunctionBounds|null> { |
| const script = rawLocation.script(); |
| if (!script) { |
| return null; |
| } |
| |
| const sourceMap = this.#sourceMapManager.sourceMapForClient(script); |
| if (!sourceMap) { |
| return null; |
| } |
| await sourceMap.waitForScopeInfo(); |
| |
| const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(rawLocation); |
| const {url, scope} = sourceMap.findOriginalFunctionScope({line: lineNumber, column: columnNumber}) ?? {}; |
| if (!scope || !url) { |
| return null; |
| } |
| |
| const project = this.#sourceMapToProject.get(sourceMap); |
| if (!project) { |
| return null; |
| } |
| |
| const uiSourceCode = project.uiSourceCodeForURL(url); |
| if (!uiSourceCode) { |
| return null; |
| } |
| |
| // If there's no original source content, callers can't get the source code for |
| // the given scope. Presently that's the only reason to get a function's bounds, |
| // so in that case return null and allow ResourceScriptMapping to fulfill this |
| // request. |
| const contentData = await uiSourceCode.requestContentData(); |
| if ('error' in contentData) { |
| return null; |
| } |
| |
| const name = scope.name ?? ''; |
| const range = |
| new TextUtils.TextRange.TextRange(scope.start.line, scope.start.column, scope.end.line, scope.end.column); |
| return new Workspace.UISourceCode.UIFunctionBounds(uiSourceCode, range, name); |
| } |
| |
| async translateRawFramesStep( |
| rawFrames: StackTraceImpl.Trie.RawFrame[], |
| translatedFrames: Awaited<ReturnType<StackTraceImpl.StackTraceModel.TranslateRawFrames>>): Promise<boolean> { |
| const frame = rawFrames[0]; |
| if (StackTraceImpl.Trie.isBuiltinFrame(frame)) { |
| return false; |
| } |
| |
| const sourceMapWithScopeInfoForFrame = async(rawFrame: StackTraceImpl.Trie.RawFrame): |
| Promise<{sourceMap: SDK.SourceMap.SourceMap, script: SDK.Script.Script}|null> => { |
| const script = this.#debuggerModel.scriptForId(rawFrame.scriptId ?? ''); |
| if (!script || this.#stubUISourceCodes.has(script)) { |
| // Use fallback while source map is being loaded. |
| return null; |
| } |
| |
| const sourceMap = script.sourceMap(); |
| await sourceMap?.waitForScopeInfo(); |
| return sourceMap?.hasScopeInfo() ? {sourceMap, script} : null; |
| }; |
| |
| const sourceMapAndScript = await sourceMapWithScopeInfoForFrame(frame); |
| if (!sourceMapAndScript) { |
| return false; |
| } |
| const {sourceMap, script} = sourceMapAndScript; |
| const {lineNumber, columnNumber} = script.relativeLocationToRawLocation(frame); |
| |
| if (!sourceMap.isOutlinedFrame(lineNumber, columnNumber)) { |
| const frames = sourceMap.translateCallSite(lineNumber, columnNumber); |
| if (!frames.length) { |
| return false; |
| } |
| |
| rawFrames.shift(); |
| const result: typeof translatedFrames[0] = []; |
| translatedFrames.push(result); |
| |
| const project = this.#sourceMapToProject.get(sourceMap); |
| for (const frame of frames) { |
| // Switch out url for UISourceCode where we have it. |
| const uiSourceCode = frame.url ? project?.uiSourceCodeForURL(frame.url) : undefined; |
| result.push({ |
| ...frame, |
| url: uiSourceCode ? undefined : frame.url, |
| uiSourceCode: uiSourceCode ?? undefined, |
| }); |
| } |
| |
| return true; |
| } |
| |
| // TODO(crbug.com/433162438): Consolidate outlined frames. |
| return false; |
| } |
| |
| /** |
| * Computes the set of line numbers which are source-mapped to a script within the |
| * given {@link uiSourceCode}. |
| * |
| * @param uiSourceCode the source mapped entity. |
| * @returns a set of source-mapped line numbers or `null` if the {@link uiSourceCode} |
| * is not provided by this {@link CompilerScriptMapping} instance. |
| */ |
| getMappedLines(uiSourceCode: Workspace.UISourceCode.UISourceCode): Set<number>|null { |
| if (!this.#uiSourceCodeToSourceMaps.has(uiSourceCode)) { |
| return null; |
| } |
| const mappedLines = new Set<number>(); |
| for (const sourceMap of this.#uiSourceCodeToSourceMaps.get(uiSourceCode)) { |
| for (const entry of sourceMap.mappings()) { |
| if (entry.sourceURL !== uiSourceCode.url()) { |
| continue; |
| } |
| mappedLines.add(entry.sourceLineNumber); |
| } |
| } |
| return mappedLines; |
| } |
| |
| /** |
| * Invoked by the {@link SDK.SourceMapManager.SourceMapManager} whenever it starts loading the |
| * source map for a given {@link SDK.Script.Script}. The `CompilerScriptMapping` will set up a |
| * {@link Workspace.UISourceCode.UISourceCode} stub for the time that the source map is being |
| * loaded to avoid showing the generated code when the DevTools front-end is stopped early (for |
| * example on a breakpoint). |
| * |
| * @param event holds the {@link SDK.Script.Script} whose source map is being loaded. |
| */ |
| private sourceMapWillAttach(event: Common.EventTarget.EventTargetEvent<{client: SDK.Script.Script}>): void { |
| const {client: script} = event.data; |
| // Create stub UISourceCode for the time source mapping is being loaded. |
| this.addStubUISourceCode(script); |
| void this.#debuggerWorkspaceBinding.updateLocations(script); |
| if (this.#ignoreListManager.isUserIgnoreListedURL(script.sourceURL, {isContentScript: script.isContentScript()})) { |
| this.#sourceMapManager.cancelAttachSourceMap(script); |
| } |
| } |
| |
| /** |
| * Invoked by the {@link SDK.SourceMapManager.SourceMapManager} after an attempt to load the |
| * source map for a given {@link SDK.Script.Script} failed. The `CompilerScriptMapping` will |
| * remove the {@link Workspace.UISourceCode.UISourceCode} stub, and from this time on won't |
| * report any mappings for the `client` script. |
| * |
| * @param event holds the {@link SDK.Script.Script} whose source map failed to load. |
| */ |
| private sourceMapFailedToAttach(event: Common.EventTarget.EventTargetEvent<{client: SDK.Script.Script}>): void { |
| const {client: script} = event.data; |
| this.removeStubUISourceCode(script); |
| void this.#debuggerWorkspaceBinding.updateLocations(script); |
| } |
| |
| /** |
| * Invoked by the {@link SDK.SourceMapManager.SourceMapManager} after an attempt to load the |
| * source map for a given {@link SDK.Script.Script} succeeded. The `CompilerScriptMapping` will |
| * now create {@link Workspace.UISourceCode.UISourceCode}s for all the sources mentioned in the |
| * `sourceMap`. |
| * |
| * In case of a conflict this method creates a new {@link Workspace.UISourceCode.UISourceCode} |
| * and copies over all the mappings from the other source maps that were registered for the |
| * same URL and which are compatible (agree on the content and ignore-list hint for the given |
| * URL). If they are considered incompatible, the original `UISourceCode` will simply be |
| * removed and a new mapping will be established. |
| * |
| * @param event holds the {@link SDK.Script.Script} and its {@link SDK.SourceMap.SourceMap}. |
| */ |
| private sourceMapAttached(event: Common.EventTarget.EventTargetEvent<{ |
| client: SDK.Script.Script, |
| sourceMap: SDK.SourceMap.SourceMap, |
| }>): void { |
| const {client: script, sourceMap} = event.data; |
| const scripts = new Set<SDK.Script.Script>([script]); |
| this.removeStubUISourceCode(script); |
| |
| const target = script.target(); |
| const projectId = `jsSourceMaps:${script.isContentScript() ? 'extensions' : ''}:${target.id()}`; |
| let project = this.#projects.get(projectId); |
| if (!project) { |
| const projectType = script.isContentScript() ? Workspace.Workspace.projectTypes.ContentScripts : |
| Workspace.Workspace.projectTypes.Network; |
| project = new ContentProviderBasedProject( |
| this.#stubProject.workspace(), projectId, projectType, /* displayName */ '', /* isServiceProject */ false); |
| NetworkProject.setTargetForProject(project, target); |
| this.#projects.set(projectId, project); |
| } |
| this.#sourceMapToProject.set(sourceMap, project); |
| |
| for (const url of sourceMap.sourceURLs()) { |
| const contentType = Common.ResourceType.resourceTypes.SourceMapScript; |
| const uiSourceCode = project.createUISourceCode(url, contentType); |
| if (sourceMap.hasIgnoreListHint(url)) { |
| uiSourceCode.markKnownThirdParty(); |
| } |
| const content = sourceMap.embeddedContentByURL(url); |
| const contentProvider = content !== null ? |
| TextUtils.StaticContentProvider.StaticContentProvider.fromString(url, contentType, content) : |
| new SDK.CompilerSourceMappingContentProvider.CompilerSourceMappingContentProvider( |
| url, contentType, script.createPageResourceLoadInitiator()); |
| let metadata: Workspace.UISourceCode.UISourceCodeMetadata|null = null; |
| if (content !== null) { |
| const encoder = new TextEncoder(); |
| metadata = new Workspace.UISourceCode.UISourceCodeMetadata(null, encoder.encode(content).length); |
| } |
| const mimeType = Common.ResourceType.ResourceType.mimeFromURL(url) ?? contentType.canonicalMimeType(); |
| |
| this.#uiSourceCodeToSourceMaps.set(uiSourceCode, sourceMap); |
| NetworkProject.setInitialFrameAttribution(uiSourceCode, script.frameId); |
| |
| // Check if there was already an `UISourceCode` for the given `url`, and if so, discard |
| // the previous one. While it would be possible to keep the previous one and just add |
| // the new mapping (from the `sourceMap`) to it as long as there's no conflict, this |
| // doesn't really work with the way the `BreakpointManager` and other parts of the front-end |
| // work, which only listen for additions/removals of `UISourceCode`s, since there's no |
| // notion of a 'UISourceCodeChanged` event (yet). |
| // |
| // Therefore, unless we discard any previous `UISourceCode` for the `url` and publish the |
| // new `uiSourceCode`, the `BreakpointManager` will not restore / set breakpoints in newly |
| // added scripts in case of code-splitting, since it won't find out about these new mappings. |
| // By removing and (re)adding a `UISourceCode` for the `url` we effectively force restoration |
| // of breakpoints. |
| const otherUISourceCode = project.uiSourceCodeForURL(url); |
| if (otherUISourceCode !== null) { |
| // Copy the existing source mappings from the `otherUISourceCode` over as long as |
| // they are compatible with the `sourceMap` wrt. `url`. While doing so, also clean |
| // up the `otherUISourceCode` (in particular its frame attributions). |
| for (const otherSourceMap of this.#uiSourceCodeToSourceMaps.get(otherUISourceCode)) { |
| this.#uiSourceCodeToSourceMaps.delete(otherUISourceCode, otherSourceMap); |
| const otherScript = this.#sourceMapManager.clientForSourceMap(otherSourceMap); |
| if (!otherScript) { |
| continue; |
| } |
| NetworkProject.removeFrameAttribution(otherUISourceCode, otherScript.frameId); |
| if (sourceMap.compatibleForURL(url, otherSourceMap)) { |
| this.#uiSourceCodeToSourceMaps.set(uiSourceCode, otherSourceMap); |
| NetworkProject.addFrameAttribution(uiSourceCode, otherScript.frameId); |
| } |
| scripts.add(otherScript); |
| } |
| project.removeUISourceCode(url); |
| } |
| |
| project.addUISourceCodeWithProvider(uiSourceCode, contentProvider, metadata, mimeType); |
| } |
| void Promise.all([...scripts].map(script => this.#debuggerWorkspaceBinding.updateLocations(script))) |
| .then(() => this.sourceMapAttachedForTest(sourceMap)); |
| } |
| |
| /** |
| * Invoked by the {@link SDK.SourceMapManager.SourceMapManager} when the source map for a given |
| * {@link SDK.Script.Script} is removed, which could be either because the target is execution |
| * context was destroyed, or the user manually asked to replace the source map for a given |
| * script. |
| * |
| * @param event holds the {@link SDK.Script.Script} and {@link SDK.SourceMap.SourceMap} that |
| * should be detached. |
| */ |
| private sourceMapDetached(event: Common.EventTarget.EventTargetEvent<{ |
| client: SDK.Script.Script, |
| sourceMap: SDK.SourceMap.SourceMap, |
| }>): void { |
| const {client: script, sourceMap} = event.data; |
| const project = this.#sourceMapToProject.get(sourceMap); |
| if (!project) { |
| return; |
| } |
| |
| // Remove all the files in the `project` that (still) belong to the `sourceMap`. |
| // In case of conflicts or overrides (for example due to HMR), not all the files |
| // that were originally provided by the `sourceMap` might still belong to it. |
| for (const uiSourceCode of project.uiSourceCodes()) { |
| if (this.#uiSourceCodeToSourceMaps.delete(uiSourceCode, sourceMap)) { |
| NetworkProject.removeFrameAttribution(uiSourceCode, script.frameId); |
| if (!this.#uiSourceCodeToSourceMaps.has(uiSourceCode)) { |
| project.removeUISourceCode(uiSourceCode.url()); |
| } |
| } |
| } |
| |
| this.#sourceMapToProject.delete(sourceMap); |
| void this.#debuggerWorkspaceBinding.updateLocations(script); |
| } |
| |
| scriptsForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): SDK.Script.Script[] { |
| const scripts = []; |
| for (const sourceMap of this.#uiSourceCodeToSourceMaps.get(uiSourceCode)) { |
| const script = this.#sourceMapManager.clientForSourceMap(sourceMap); |
| if (script) { |
| scripts.push(script); |
| } |
| } |
| return scripts; |
| } |
| |
| private sourceMapAttachedForTest(_sourceMap: SDK.SourceMap.SourceMap): void { |
| } |
| |
| dispose(): void { |
| Common.EventTarget.removeEventListeners(this.#eventListeners); |
| for (const project of this.#projects.values()) { |
| project.dispose(); |
| } |
| this.#stubProject.dispose(); |
| } |
| } |