diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index 96acacb04dcf73..76b3291d7de9f9 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -186,6 +186,17 @@ export async function mono_download_assets (): Promise { const instantiate = async (downloadPromise: Promise) => { const asset = await downloadPromise; + const headersOnly = skipBufferByAssetTypes[asset.behavior]; + + if (headersOnly) { + if (asset.behavior === "symbols") { + await runtimeHelpers.instantiate_symbols_asset(asset); + cleanupAsset(asset); + } + ++loaderHelpers.actual_downloaded_assets_count; + return; + } + if (asset.buffer) { if (!skipInstantiateByAssetTypes[asset.behavior]) { mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these"); @@ -202,24 +213,12 @@ export async function mono_download_assets (): Promise { runtimeHelpers.instantiate_asset(asset, url, data); } } else { - const headersOnly = skipBufferByAssetTypes[asset.behavior]; - if (!headersOnly) { - mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_downloaded_assets_count--; - } - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { - loaderHelpers.expected_instantiated_assets_count--; - } - } else { - if (asset.behavior === "symbols") { - await runtimeHelpers.instantiate_symbols_asset(asset); - cleanupAsset(asset); - } - - if (skipBufferByAssetTypes[asset.behavior]) { - ++loaderHelpers.actual_downloaded_assets_count; - } + mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_downloaded_assets_count--; + } + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + loaderHelpers.expected_instantiated_assets_count--; } } }; @@ -529,9 +528,7 @@ async function start_asset_download_sources (asset: AssetEntryInternal): Promise ok: true, arrayBuffer: () => buffer, json: () => JSON.parse(new TextDecoder("utf-8").decode(buffer)), - text: () => { - throw new Error("NotImplementedException"); - }, + text: () => new TextDecoder("utf-8").decode(buffer), headers: { get: () => undefined, } diff --git a/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs index f999abce98ba53..4e6a05cedf4bd6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs @@ -121,6 +121,23 @@ public async Task AssetIntegrity() ); } + [ConditionalFact(typeof(BuildTestBase), nameof(IsMonoRuntime)), TestCategory("bundler-friendly")] + public async Task BufferedAssetsTest() + { + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset( + config, + aot: false, + TestAsset.WasmBasicTestApp, + "ModuleConfigTests_BufferedAssetsTest", + extraProperties: "true"); + PublishProject(info, config, new PublishOptions(AssertAppBundle: false)); + await RunForPublishWithWebServer(new BrowserRunOptions( + Configuration: config, + TestScenario: "BufferedAssetsTest" + )); + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 74f9e4fdee2b7e..c1480d70fa91ee 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -112,9 +112,6 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles( foreach ((string expectedFilename, bool expectFingerprint) in superSet.OrderByDescending(kvp => kvp.Key)) { - string prefix = Path.GetFileNameWithoutExtension(expectedFilename); - string extension = Path.GetExtension(expectedFilename).Substring(1); - dotnetFiles = dotnetFiles .Where(actualFile => { @@ -127,7 +124,7 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles( expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs, expectFingerprintForThisFile: expectFingerprint)) { - string pattern = $"^{prefix}{s_dotnetVersionHashRegex}{extension}$"; + string pattern = GetFingerprintRegexPattern(expectedFilename); var match = Regex.Match(actualFilename, pattern); if (!match.Success) return true; @@ -418,6 +415,19 @@ private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, public bool ShouldCheckFingerprint(string expectedFilename, bool? expectFingerprintOnDotnetJs, bool expectFingerprintForThisFile) => IsFingerprintingEnabled && ((expectedFilename == "dotnet.js" && expectFingerprintOnDotnetJs == true) || expectFingerprintForThisFile); + private static string GetFingerprintRegexPattern(string expectedFilename) + { + const string jsSymbolsSuffix = ".js.symbols"; + if (expectedFilename.EndsWith(jsSymbolsSuffix, StringComparison.Ordinal)) + { + string prefix = expectedFilename[..^jsSymbolsSuffix.Length]; + return $"^{Regex.Escape(prefix)}{s_dotnetVersionHashRegex}js\\.symbols$"; + } + + string defaultPrefix = Path.GetFileNameWithoutExtension(expectedFilename); + string extension = Path.GetExtension(expectedFilename).Substring(1); + return $"^{Regex.Escape(defaultPrefix)}{s_dotnetVersionHashRegex}{Regex.Escape(extension)}$"; + } public static void AssertRuntimePackPath(string buildOutput, string targetFramework, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded) { @@ -614,14 +624,11 @@ public BootJsonData AssertBootJson(AssertBundleOptions options) bool expectFingerprint = knownSet[expectedFilename]; expectedEntries[expectedFilename] = item => { - string prefix = Path.GetFileNameWithoutExtension(expectedFilename); - string extension = Path.GetExtension(expectedFilename).Substring(1); - if (ShouldCheckFingerprint(expectedFilename: expectedFilename, expectFingerprintOnDotnetJs: options.ExpectDotnetJsFingerprinting, expectFingerprintForThisFile: expectFingerprint)) { - return Regex.Match(item, $"{prefix}{s_dotnetVersionHashRegex}{extension}").Success; + return Regex.Match(item, GetFingerprintRegexPattern(expectedFilename)).Success; } else { diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index 57f299cc3310bc..901a2a9e6fe5ef 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -216,6 +216,26 @@ switch (testCase) { case "MainWithArgs": dotnet.withApplicationArgumentsFromQuery(); break; + case "BufferedAssetsTest": + const originalFetch4 = globalThis.fetch.bind(globalThis); + dotnet.withModuleConfig({ + onConfigLoaded: (config) => { + const bufferedAssets = [ + ...config.resources.wasmNative, + ...config.resources.assembly, + ...(config.resources.pdb ?? []), + ...config.resources.wasmSymbols, + ]; + for (const asset of bufferedAssets) { + const url = new URL(asset.resolvedUrl ?? `./_framework/${asset.name}`, location.href); + asset.buffer = originalFetch4(url).then(r => { + if (!r.ok) throw new Error(`Failed to fetch buffered asset '${url}': ${r.status} ${r.statusText}`); + return r.arrayBuffer(); + }); + } + } + }); + break; } const { setModuleImports, Module, getAssemblyExports, getConfig, INTERNAL, invokeLibraryInitializers } = await dotnet.create(); @@ -403,6 +423,9 @@ try { exit(foundB && retB == 42 ? 0 : 1); + break; + case "BufferedAssetsTest": + await dotnet.runMainAndExit(); break; default: console.error(`Unknown test case: ${testCase}`);