From 0e57a180e0a0ff79a0e9f2a046ac0f333497fb69 Mon Sep 17 00:00:00 2001 From: sid sri Date: Fri, 24 Jul 2026 19:22:16 +0530 Subject: [PATCH] fix: reuse sources registered during URL resolution --- src/adapter/sourceContainer.test.ts | 81 +++++++++++++++++++++++++++++ src/adapter/sourceContainer.ts | 6 +++ 2 files changed, 87 insertions(+) create mode 100644 src/adapter/sourceContainer.test.ts diff --git a/src/adapter/sourceContainer.test.ts b/src/adapter/sourceContainer.test.ts new file mode 100644 index 000000000..cd043794e --- /dev/null +++ b/src/adapter/sourceContainer.test.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { expect } from 'chai'; +import Cdp from '../cdp/api'; +import { ILogger } from '../common/logging'; +import { upcastPartial } from '../common/objUtils'; +import { getDeferred } from '../common/promiseUtil'; +import { ISourceMapFactory } from '../common/sourceMaps/sourceMapFactory'; +import { ISourcePathResolver } from '../common/sourcePathResolver'; +import { AnyLaunchConfiguration } from '../configuration'; +import Dap from '../dap/api'; +import { stubbedDapApi } from '../dap/stubbedApi'; +import { IWasmSymbolProvider } from './dwarf/wasmSymbolProvider'; +import { IResourceProvider } from './resourceProvider'; +import { ScriptSkipper } from './scriptSkipper/implementation'; +import { SourceContainer } from './sourceContainer'; + +describe('SourceContainer', () => { + it('reuses a source registered while its URL was resolving', async () => { + const firstResolution = getDeferred(); + const secondResolution = getDeferred(); + const resolutions = [firstResolution.promise, secondResolution.promise]; + const sourcePathResolver = upcastPartial({ + urlToAbsolutePath: () => resolutions.shift() ?? Promise.resolve(undefined), + }); + const scriptSkipper = upcastPartial({ + setSourceContainer: () => undefined, + initializeSkippingValueForSource: () => undefined, + }); + const sourceContainer = new SourceContainer( + stubbedDapApi().actual, + upcastPartial({}), + upcastPartial({ + verbose: () => undefined, + assert: (value: T | false | null | undefined): value is T => !!value, + }), + upcastPartial({ + sourceMaps: true, + timeouts: {}, + }), + upcastPartial({}), + sourcePathResolver, + scriptSkipper, + upcastPartial({}), + upcastPartial({}), + ); + const event = (scriptId: string) => + upcastPartial({ + scriptId, + url: 'file:///shared.js', + hash: 'same-content', + }); + + const firstAdd = sourceContainer.addSource( + event('1'), + async () => undefined, + undefined, + undefined, + undefined, + 'same-content', + ); + const secondAdd = sourceContainer.addSource( + event('2'), + async () => undefined, + undefined, + undefined, + undefined, + 'same-content', + ); + + firstResolution.resolve(undefined); + const firstSource = await firstAdd; + secondResolution.resolve(undefined); + const secondSource = await secondAdd; + + expect(secondSource).to.equal(firstSource); + expect(sourceContainer.getSourceByOriginalUrl(event('1').url)).to.equal(firstSource); + }); +}); diff --git a/src/adapter/sourceContainer.ts b/src/adapter/sourceContainer.ts index e91cbb153..9120c9bd0 100644 --- a/src/adapter/sourceContainer.ts +++ b/src/adapter/sourceContainer.ts @@ -626,6 +626,12 @@ export class SourceContainer { ): Promise { const absolutePath = await this.sourcePathResolver.urlToAbsolutePath({ url: event.url }); + // Another script event may have registered this source while the URL was resolving. + const existingSource = contentHash && this._sourceByOriginalUrl.get(event.url); + if (existingSource && existingSource.contentHash === contentHash) { + return existingSource; + } + this.logger.verbose(LogTag.RuntimeSourceCreate, 'Creating source from url', { inputUrl: event.url, absolutePath,