[browser] app start benchmark fix, loadBootResource fix (#89857)
authorPavel Savara <pavel.savara@gmail.com>
Tue, 8 Aug 2023 12:00:57 +0000 (14:00 +0200)
committerGitHub <noreply@github.com>
Tue, 8 Aug 2023 12:00:57 +0000 (14:00 +0200)
Co-authored-by: Ankit Jain <radical@gmail.com>
19 files changed:
src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj
src/mono/sample/wasm/browser-advanced/advanced-sample.lib.module.js [new file with mode: 0644]
src/mono/sample/wasm/browser-advanced/index.html
src/mono/sample/wasm/browser-advanced/main.js
src/mono/sample/wasm/browser-bench/appstart-frame.html
src/mono/sample/wasm/simple-server/Program.cs
src/mono/wasm/features.md
src/mono/wasm/runtime/dotnet.d.ts
src/mono/wasm/runtime/loader/assets.ts
src/mono/wasm/runtime/loader/assetsCache.ts
src/mono/wasm/runtime/loader/config.ts
src/mono/wasm/runtime/loader/globals.ts
src/mono/wasm/runtime/loader/libraryInitializers.ts
src/mono/wasm/runtime/loader/run.ts
src/mono/wasm/runtime/pthreads/shared/emscripten-replacements.ts
src/mono/wasm/runtime/startup.ts
src/mono/wasm/runtime/types/export-types.ts
src/mono/wasm/runtime/types/index.ts
src/mono/wasm/runtime/types/internal.ts

index fee0941..a16c120 100644 (file)
@@ -21,6 +21,7 @@
   </PropertyGroup>
   <ItemGroup>
     <WasmExtraFilesToDeploy Include="main.js" />
+    <WasmExtraFilesToDeploy Include="advanced-sample.lib.module.js" />
     <!-- add export GL object from Module -->
     <EmccExportedRuntimeMethod Include="GL" />
     <NativeFileReference Include="fibonacci.c" />
diff --git a/src/mono/sample/wasm/browser-advanced/advanced-sample.lib.module.js b/src/mono/sample/wasm/browser-advanced/advanced-sample.lib.module.js
new file mode 100644 (file)
index 0000000..be57002
--- /dev/null
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+export function onRuntimeConfigLoaded(config) {
+    console.log("advanced-sample onRuntimeConfigLoaded")
+}
+
+export async function onRuntimeReady({ getAssemblyExports, getConfig }) {
+    console.log("advanced-sample onRuntimeReady")
+}
\ No newline at end of file
index eb1032f..c8961d7 100644 (file)
@@ -9,14 +9,14 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval';" />
   <script type='module' src="./main.js"></script>
-  <script type='module' src="./_framework/dotnet.js"></script>
-  <link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="anonymous">
+  <script type='module' src="./dotnet.js"></script>
+  <link rel="preload" href="./blazor.boot.json" as="fetch" crossorigin="use-credentials">
   <link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
   <link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
   <link rel="prefetch" href="./dotnet.native.wasm" as="fetch" crossorigin="anonymous">
   <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
   <link rel="prefetch" href="./icudt.dat" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./managed/System.Private.CoreLib.wasm" as="fetch" crossorigin="anonymous">
+  <link rel="prefetch" href="./System.Private.CoreLib.wasm" as="fetch" crossorigin="anonymous">
 </head>
 
 <body>
index 5dbbf75..7b58f77 100644 (file)
@@ -38,6 +38,9 @@ try {
                 // config is loaded and could be tweaked before the rest of the runtime startup sequence
                 config.environmentVariables["MONO_LOG_LEVEL"] = "debug";
                 config.browserProfilerOptions = {};
+                config.resources.modulesAfterConfigLoaded = {
+                    "advanced-sample.lib.module.js": ""
+                }
             },
             preInit: () => { console.log('user code Module.preInit'); },
             preRun: () => { console.log('user code Module.preRun'); },
index 697f7ac..5bb75e3 100644 (file)
@@ -9,12 +9,12 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <script type="module" src="./frame-main.js"></script>
   <script type='module' src="./_framework/dotnet.js"></script>
-  <link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./dotnet.native.js" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./dotnet.runtime.js" as="fetch" crossorigin="anonymous">
-  <link rel="prefetch" href="./dotnet.native.wasm" as="fetch" crossorigin="anonymous">
+  <link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
+  <link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
+  <link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
+  <link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
   <!-- users should consider if they optimize for the first load or subsequent load from memory snapshot -->
-  <link rel="prefetch" href="./managed/System.Private.CoreLib.dll" as="fetch" crossorigin="anonymous">
+  <link rel="prefetch" href="./_framework/System.Private.CoreLib.dll" as="fetch" crossorigin="anonymous">
 </head>
 
 <body>
index 64e6e10..e1a520d 100644 (file)
@@ -6,6 +6,7 @@ using System.Diagnostics;
 using System.Collections.Concurrent;
 using System.Runtime.InteropServices;
 using System;
+using System.Security.Cryptography;
 
 namespace HttpServer
 {
@@ -16,11 +17,13 @@ namespace HttpServer
         public int Finished { get; set; } = 0;
     }
 
+    public sealed record FileContent(byte[] buffer, string hash);
+
     public sealed class Program
     {
         private bool Verbose = false;
         private ConcurrentDictionary<string, Session> Sessions = new ConcurrentDictionary<string, Session>();
-        private Dictionary<string, byte[]> cache = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
+        private Dictionary<string, FileContent> cache = new(StringComparer.OrdinalIgnoreCase);
 
         public static int Main()
         {
@@ -104,7 +107,7 @@ namespace HttpServer
                 ReceivePostAsync(context);
         }
 
-        private async Task<byte[]?> GetFileContent(string path)
+        private async Task<FileContent?> GetFileContent(string path)
         {
             if (Verbose)
                 await Console.Out.WriteLineAsync($"get content for: {path}");
@@ -124,9 +127,13 @@ namespace HttpServer
             if (Verbose)
                 await Console.Out.WriteLineAsync($"adding content to cache for: {path}");
 
-            cache[path] = content;
+            using HashAlgorithm hashAlgorithm = SHA256.Create();
+            byte[] hash = hashAlgorithm.ComputeHash(content);
+            var fc = new FileContent(content, "sha256-" + Convert.ToBase64String(hash));
+
+            cache[path] = fc;
 
-            return content;
+            return fc;
         }
 
         private async void ReceivePostAsync(HttpListenerContext context)
@@ -189,14 +196,14 @@ namespace HttpServer
             else if (path.StartsWith("/"))
                 path = path.Substring(1);
 
-            byte[]? buffer;
+            FileContent? fc;
             try
             {
-                buffer = await GetFileContent(path);
+                fc = await GetFileContent(path);
 
-                if (buffer != null && throttleMbps > 0)
+                if (fc != null && throttleMbps > 0)
                 {
-                    double delaySeconds = (buffer.Length * 8) / (throttleMbps * 1024 * 1024);
+                    double delaySeconds = (fc.buffer.Length * 8) / (throttleMbps * 1024 * 1024);
                     int delayMs = (int)(delaySeconds * 1000);
                     if (session != null)
                     {
@@ -246,12 +253,20 @@ namespace HttpServer
                     }
                 }
             }
-            catch (Exception)
+            catch (System.IO.DirectoryNotFoundException)
+            {
+                if (Verbose)
+                    Console.WriteLine($"Not found: {path}");
+                fc = null;
+            }
+            catch (Exception ex)
             {
-                buffer = null;
+                if (Verbose)
+                    Console.WriteLine($"Exception: {ex}");
+                fc = null;
             }
 
-            if (buffer != null)
+            if (fc != null)
             {
                 string? contentType = null;
                 if (path.EndsWith(".wasm"))
@@ -270,7 +285,7 @@ namespace HttpServer
                 {
                     Console.WriteLine("Faking 500 " + url);
                     context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
-                    await stream.WriteAsync(buffer, 0, 0).ConfigureAwait(false);
+                    await stream.WriteAsync(fc.buffer, 0, 0).ConfigureAwait(false);
                     await stream.FlushAsync();
                     context.Response.Close();
                     return;
@@ -279,25 +294,36 @@ namespace HttpServer
                 if (contentType != null)
                     context.Response.ContentType = contentType;
 
-                context.Response.ContentLength64 = buffer.Length;
-                context.Response.AppendHeader("cache-control", "public, max-age=31536000");
+                // context.Response.AppendHeader("cache-control", "public, max-age=31536000");
                 context.Response.AppendHeader("Cross-Origin-Embedder-Policy", "require-corp");
                 context.Response.AppendHeader("Cross-Origin-Opener-Policy", "same-origin");
+                context.Response.AppendHeader("ETag", fc.hash);
 
                 // test download re-try
                 if (url.Query.Contains("testAbort"))
                 {
                     Console.WriteLine("Faking abort " + url);
-                    await stream.WriteAsync(buffer, 0, 10).ConfigureAwait(false);
+                    context.Response.ContentLength64 = fc.buffer.Length;
+                    await stream.WriteAsync(fc.buffer, 0, 10).ConfigureAwait(false);
                     await stream.FlushAsync();
                     await Task.Delay(100);
                     context.Response.Abort();
                     return;
                 }
+                var ifNoneMatch = context.Request.Headers.Get("If-None-Match");
+                if (ifNoneMatch == fc.hash)
+                {
+                    context.Response.StatusCode = 304;
+                    await stream.FlushAsync();
+                    stream.Close();
+                    context.Response.Close();
+                    return;
+                }
 
                 try
                 {
-                    await stream.WriteAsync(buffer).ConfigureAwait(false);
+                    context.Response.ContentLength64 = fc.buffer.Length;
+                    await stream.WriteAsync(fc.buffer).ConfigureAwait(false);
                 }
                 catch (Exception e)
                 {
index 74f6ba6..afdcc14 100644 (file)
@@ -191,7 +191,7 @@ See also [fetch integrity on MDN](https://developer.mozilla.org/en-US/docs/Web/A
 In order to start downloading application resources as soon as possible you can add HTML elements to `<head>` of your page similar to:
 
 ```html
-<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="anonymous">
+<link rel="preload" href="./_framework/blazor.boot.json" as="fetch" crossorigin="use-credentials">
 <link rel="prefetch" href="./_framework/dotnet.native.js" as="fetch" crossorigin="anonymous">
 <link rel="prefetch" href="./_framework/dotnet.runtime.js" as="fetch" crossorigin="anonymous">
 <link rel="prefetch" href="./_framework/dotnet.native.wasm" as="fetch" crossorigin="anonymous">
index c758978..2f8f000 100644 (file)
@@ -130,6 +130,14 @@ type MonoConfig = {
      */
     cacheBootResources?: boolean;
     /**
+     * Configures use of the `integrity` directive for fetching assets
+     */
+    disableIntegrityCheck?: boolean;
+    /**
+     * Configures use of the `no-cache` directive for fetching assets
+     */
+    disableNoCacheFetch?: boolean;
+    /**
     * Enables diagnostic log messages during startup
     */
     diagnosticTracing?: boolean;
@@ -212,18 +220,28 @@ type ResourceList = {
  * When returned string is not qualified with `./` or absolute URL, it will be resolved against the application base URI.
  */
 type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string, behavior: AssetBehaviors) => string | Promise<Response> | null | undefined;
-interface ResourceRequest {
-    name: string;
-    behavior: AssetBehaviors;
-    resolvedUrl?: string;
-    hash?: string | null | "";
-}
 interface LoadingResource {
     name: string;
     url: string;
     response: Promise<Response>;
 }
-interface AssetEntry extends ResourceRequest {
+interface AssetEntry {
+    /**
+     * the name of the asset, including extension.
+     */
+    name: string;
+    /**
+     * determines how the asset will be handled once loaded
+     */
+    behavior: AssetBehaviors;
+    /**
+     * this should be absolute url to the asset
+     */
+    resolvedUrl?: string;
+    /**
+     * the integrity hash of the asset (if any)
+     */
+    hash?: string | null | "";
     /**
      * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded.
      */
@@ -442,4 +460,4 @@ declare global {
 }
 declare const createDotnetRuntime: CreateDotnetRuntimeType;
 
-export { AssetBehaviors, AssetEntry, CreateDotnetRuntimeType, DotnetHostBuilder, DotnetModuleConfig, EmscriptenModule, GlobalizationMode, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, createDotnetRuntime as default, dotnet, exit };
+export { AssetBehaviors, AssetEntry, CreateDotnetRuntimeType, DotnetHostBuilder, DotnetModuleConfig, EmscriptenModule, GlobalizationMode, IMemoryView, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit };
index 339ae32..b39ce6a 100644 (file)
@@ -1,23 +1,27 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+import MonoWasmThreads from "consts:monoWasmThreads";
+
 import type { AssetEntryInternal, PromiseAndController } from "../types/internal";
-import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, ResourceRequest, SingleAssetBehaviors as SingleAssetBehaviors, WebAssemblyBootResourceType } from "../types";
+import type { AssetBehaviors, AssetEntry, LoadingResource, ResourceList, SingleAssetBehaviors as SingleAssetBehaviors, WebAssemblyBootResourceType } from "../types";
 import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, mono_assert, runtimeHelpers } from "./globals";
 import { createPromiseController } from "./promise-controller";
 import { mono_log_debug } from "./logging";
 import { mono_exit } from "./exit";
-import { addCachedReponse, findCachedResponse, isCacheAvailable } from "./assetsCache";
+import { addCachedReponse, findCachedResponse } from "./assetsCache";
 import { getIcuResourceName } from "./icu";
-import { mono_log_warn } from "./logging";
 import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
 
 
 let throttlingPromise: PromiseAndController<void> | undefined;
 // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time
 let parallel_count = 0;
+const containedInSnapshotAssets: AssetEntryInternal[] = [];
+const alwaysLoadedAssets: AssetEntryInternal[] = [];
+const singleAssets: Map<string, AssetEntryInternal> = new Map();
 
-const jsModulesAssetTypes: {
+const jsRuntimeModulesAssetTypes: {
     [k: string]: boolean
 } = {
     "js-module-threads": true,
@@ -26,6 +30,30 @@ const jsModulesAssetTypes: {
     "js-module-native": true,
 };
 
+const jsModulesAssetTypes: {
+    [k: string]: boolean
+} = {
+    ...jsRuntimeModulesAssetTypes,
+    "js-module-library-initializer": true,
+};
+
+const singleAssetTypes: {
+    [k: string]: boolean
+} = {
+    ...jsRuntimeModulesAssetTypes,
+    "dotnetwasm": true,
+    "heap": true,
+    "manifest": true,
+};
+
+// append query to asset url to prevent reusing state
+const appendQueryAssetTypes: {
+    [k: string]: boolean
+} = {
+    ...jsModulesAssetTypes,
+    "manifest": true,
+};
+
 // don't `fetch` javaScript and wasm files
 const skipDownloadsByAssetTypes: {
     [k: string]: boolean
@@ -67,45 +95,55 @@ export function shouldLoadIcuAsset(asset: AssetEntryInternal): boolean {
     return !(asset.behavior == "icu" && asset.name != loaderHelpers.preferredIcuAsset);
 }
 
-function getSingleAssetWithResolvedUrl(resources: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntry {
-    const keys = Object.keys(resources || {});
+function convert_single_asset(modulesAssets: AssetEntryInternal[], resource: ResourceList | undefined, behavior: SingleAssetBehaviors): AssetEntryInternal {
+    const keys = Object.keys(resource || {});
     mono_assert(keys.length == 1, `Expect to have one ${behavior} asset in resources`);
 
     const name = keys[0];
+
     const asset = {
         name,
-        hash: resources![name],
+        hash: resource![name],
         behavior,
-        resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name), behavior)
     };
 
-    const customSrc = invokeLoadBootResource(asset);
-    if (typeof (customSrc) === "string") {
-        asset.resolvedUrl = makeURLAbsoluteWithApplicationBase(customSrc);
-    } else if (customSrc) {
-        mono_log_warn(`For ${behavior} resource: ${name}, custom loaders must supply a URI string.`);
-        // we apply a default URL
+    set_single_asset(asset);
+
+    // so that we can use it on the worker too
+    modulesAssets.push(asset);
+    return asset;
+}
+
+function set_single_asset(asset: AssetEntryInternal) {
+    if (singleAssetTypes[asset.behavior]) {
+        singleAssets.set(asset.behavior, asset);
     }
+}
 
+function get_single_asset(behavior: SingleAssetBehaviors): AssetEntryInternal {
+    mono_assert(singleAssetTypes[behavior], `Unknown single asset behavior ${behavior}`);
+    const asset = singleAssets.get(behavior);
+    mono_assert(asset, `Single asset for ${behavior} not found`);
     return asset;
 }
 
 export function resolve_single_asset_path(behavior: SingleAssetBehaviors): AssetEntryInternal {
-    const resources = loaderHelpers.config.resources;
-    mono_assert(resources, "Can't find resources in config");
-
-    switch (behavior) {
-        case "dotnetwasm":
-            return getSingleAssetWithResolvedUrl(resources.wasmNative, behavior);
-        case "js-module-threads":
-            return getSingleAssetWithResolvedUrl(resources.jsModuleWorker, behavior);
-        case "js-module-native":
-            return getSingleAssetWithResolvedUrl(resources.jsModuleNative, behavior);
-        case "js-module-runtime":
-            return getSingleAssetWithResolvedUrl(resources.jsModuleRuntime, behavior);
-        default:
-            throw new Error(`Unknown single asset behavior ${behavior}`);
+    const asset = get_single_asset(behavior);
+    asset.resolvedUrl = loaderHelpers.locateFile(asset.name);
+
+    if (jsRuntimeModulesAssetTypes[asset.behavior]) {
+        // give loadBootResource chance to override the url for JS modules with 'dotnetjs' type
+        const customLoadResult = invokeLoadBootResource(asset);
+        if (customLoadResult) {
+            mono_assert(typeof customLoadResult === "string", "loadBootResource response for 'dotnetjs' type should be a URL string");
+            asset.resolvedUrl = customLoadResult;
+        } else {
+            asset.resolvedUrl = appendUniqueQuery(asset.resolvedUrl, asset.behavior);
+        }
+    } else if (asset.behavior !== "dotnetwasm") {
+        throw new Error(`Unknown single asset behavior ${behavior}`);
     }
+    return asset;
 }
 
 export async function mono_download_assets(): Promise<void> {
@@ -113,12 +151,8 @@ export async function mono_download_assets(): Promise<void> {
     loaderHelpers.maxParallelDownloads = loaderHelpers.config.maxParallelDownloads || loaderHelpers.maxParallelDownloads;
     loaderHelpers.enableDownloadRetry = loaderHelpers.config.enableDownloadRetry || loaderHelpers.enableDownloadRetry;
     try {
-        const alwaysLoadedAssets: AssetEntryInternal[] = [];
-        const containedInSnapshotAssets: AssetEntryInternal[] = [];
         const promises_of_assets: Promise<AssetEntryInternal>[] = [];
 
-        prepareAssets(containedInSnapshotAssets, alwaysLoadedAssets);
-
         const countAndStartDownload = (asset: AssetEntryInternal) => {
             if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) {
                 loaderHelpers.expected_instantiated_assets_count++;
@@ -222,13 +256,13 @@ export async function mono_download_assets(): Promise<void> {
     }
 }
 
-function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLoadedAssets: AssetEntryInternal[]) {
+export function prepareAssets() {
     const config = loaderHelpers.config;
+    const modulesAssets: AssetEntryInternal[] = [];
 
     // if assets exits, we will assume Net7 legacy and not process resources object
     if (config.assets) {
-        for (const a of config.assets) {
-            const asset: AssetEntryInternal = a;
+        for (const asset of config.assets) {
             mono_assert(typeof asset === "object", () => `asset must be object, it was ${typeof asset} : ${asset}`);
             mono_assert(typeof asset.behavior === "string", "asset behavior must be known string");
             mono_assert(typeof asset.name === "string", "asset name must be string");
@@ -240,9 +274,22 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo
             } else {
                 alwaysLoadedAssets.push(asset);
             }
+            set_single_asset(asset);
         }
     } else if (config.resources) {
         const resources = config.resources;
+
+        mono_assert(resources.wasmNative, "resources.wasmNative must be defined");
+        mono_assert(resources.jsModuleNative, "resources.jsModuleNative must be defined");
+        mono_assert(resources.jsModuleRuntime, "resources.jsModuleRuntime must be defined");
+        mono_assert(!MonoWasmThreads || resources.jsModuleWorker, "resources.jsModuleWorker must be defined");
+        convert_single_asset(modulesAssets, resources.wasmNative, "dotnetwasm");
+        convert_single_asset(modulesAssets, resources.jsModuleNative, "js-module-native");
+        convert_single_asset(modulesAssets, resources.jsModuleRuntime, "js-module-runtime");
+        if (MonoWasmThreads) {
+            convert_single_asset(modulesAssets, resources.jsModuleWorker, "js-module-threads");
+        }
+
         if (resources.assembly) {
             for (const name in resources.assembly) {
                 containedInSnapshotAssets.push({
@@ -314,21 +361,34 @@ function prepareAssets(containedInSnapshotAssets: AssetEntryInternal[], alwaysLo
         }
     }
 
+    // FIXME: should we also load Net7 backward compatible `config.configs` in a same way ?
     if (config.appsettings) {
         for (let i = 0; i < config.appsettings.length; i++) {
             const configUrl = config.appsettings[i];
             const configFileName = fileName(configUrl);
             if (configFileName === "appsettings.json" || configFileName === `appsettings.${config.applicationEnvironment}.json`) {
                 alwaysLoadedAssets.push({
-                    name: configFileName,
-                    resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(configUrl), "vfs"),
-                    behavior: "vfs"
+                    name: configUrl,
+                    behavior: "vfs",
+                    // TODO what should be the virtualPath ?
+                    noCache: true,
+                    useCredentials: true
                 });
             }
+            // FIXME: why are not loading all the other named files in appsettings ? https://github.com/dotnet/runtime/issues/89861
         }
     }
 
-    config.assets = [...containedInSnapshotAssets, ...alwaysLoadedAssets];
+    config.assets = [...containedInSnapshotAssets, ...alwaysLoadedAssets, ...modulesAssets];
+}
+
+export function prepareAssetsWorker() {
+    const config = loaderHelpers.config;
+    mono_assert(config.assets, "config.assets must be defined");
+
+    for (const asset of config.assets) {
+        set_single_asset(asset);
+    }
 }
 
 export function delay(ms: number): Promise<void> {
@@ -524,7 +584,7 @@ function resolve_path(asset: AssetEntry, sourcePrefix: string): string {
 
 export function appendUniqueQuery(attemptUrl: string, behavior: AssetBehaviors): string {
     // apply unique query to js modules to make the module state independent of the other runtime instances
-    if (loaderHelpers.modulesUniqueQuery && jsModulesAssetTypes[behavior]) {
+    if (loaderHelpers.modulesUniqueQuery && appendQueryAssetTypes[behavior]) {
         attemptUrl = attemptUrl + loaderHelpers.modulesUniqueQuery;
     }
 
@@ -534,16 +594,16 @@ export function appendUniqueQuery(attemptUrl: string, behavior: AssetBehaviors):
 let resourcesLoaded = 0;
 const totalResources = new Set<string>();
 
-function download_resource(request: ResourceRequest): LoadingResource {
+function download_resource(asset: AssetEntryInternal): LoadingResource {
     try {
-        mono_assert(request.resolvedUrl, "Request's resolvedUrl must be set");
-        const fetchResponse = download_resource_with_cache(request);
-        const response = { name: request.name, url: request.resolvedUrl, response: fetchResponse };
+        mono_assert(asset.resolvedUrl, "Request's resolvedUrl must be set");
+        const fetchResponse = download_resource_with_cache(asset);
+        const response = { name: asset.name, url: asset.resolvedUrl, response: fetchResponse };
 
-        totalResources.add(request.name!);
+        totalResources.add(asset.name!);
         response.response.then(() => {
-            if (request.behavior == "assembly") {
-                loaderHelpers.loadedAssemblies.push(request.resolvedUrl!);
+            if (asset.behavior == "assembly") {
+                loaderHelpers.loadedAssemblies.push(asset.resolvedUrl!);
             }
 
             resourcesLoaded++;
@@ -554,56 +614,57 @@ function download_resource(request: ResourceRequest): LoadingResource {
     } catch (err) {
         const response = <Response><any>{
             ok: false,
-            url: request.resolvedUrl,
+            url: asset.resolvedUrl,
             status: 500,
             statusText: "ERR29: " + err,
             arrayBuffer: () => { throw err; },
             json: () => { throw err; }
         };
         return {
-            name: request.name, url: request.resolvedUrl!, response: Promise.resolve(response)
+            name: asset.name, url: asset.resolvedUrl!, response: Promise.resolve(response)
         };
     }
 }
 
-async function download_resource_with_cache(request: ResourceRequest): Promise<Response> {
-    let response = await findCachedResponse(request);
+async function download_resource_with_cache(asset: AssetEntryInternal): Promise<Response> {
+    let response = await findCachedResponse(asset);
     if (!response) {
-        response = await fetchResource(request);
-        addCachedReponse(request, response);
+        response = await fetchResource(asset);
+        addCachedReponse(asset, response);
     }
 
     return response;
 }
 
-const credentialsIncludeAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration
-
-function fetchResource(request: ResourceRequest): Promise<Response> {
+function fetchResource(asset: AssetEntryInternal): Promise<Response> {
     // Allow developers to override how the resource is loaded
-    let url = request.resolvedUrl!;
+    let url = asset.resolvedUrl!;
     if (loaderHelpers.loadBootResource) {
-        const customLoadResult = invokeLoadBootResource(request);
+        const customLoadResult = invokeLoadBootResource(asset);
         if (customLoadResult instanceof Promise) {
             // They are supplying an entire custom response, so just use that
             return customLoadResult;
         } else if (typeof customLoadResult === "string") {
-            url = makeURLAbsoluteWithApplicationBase(customLoadResult);
+            url = customLoadResult;
         }
     }
 
-    const fetchOptions: RequestInit = {
-        cache: "no-cache"
-    };
-
-    if (credentialsIncludeAssetBehaviors.includes(request.behavior)) {
+    const fetchOptions: RequestInit = {};
+    if (!loaderHelpers.config.disableNoCacheFetch) {
+        // FIXME: "no-cache" is how blazor works in Net7, but this prevents caching on HTTP level
+        // if we would like to get rid of our own cache and only use HTTP cache, we need to remove this
+        // https://github.com/dotnet/runtime/issues/74815
+        fetchOptions.cache = "no-cache";
+    }
+    if (asset.useCredentials) {
         // Include credentials so the server can allow download / provide user specific file
         fetchOptions.credentials = "include";
     } else {
-        // Any other resource than configuration should provide integrity check
-        // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking
-        // This is to give developers an easy opt-out from the entire caching/validation flow if
-        // there's anything they don't like about it.
-        fetchOptions.integrity = isCacheAvailable() ? (request.hash ?? "") : undefined;
+        // `disableIntegrityCheck` is to give developers an easy opt-out from the integrity check 
+        if (!loaderHelpers.config.disableIntegrityCheck && asset.hash) {
+            // Any other resource than configuration should provide integrity check
+            fetchOptions.integrity = asset.hash;
+        }
     }
 
     return loaderHelpers.fetch_like(url, fetchOptions);
@@ -623,14 +684,18 @@ const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | u
     "js-module-threads": "dotnetjs"
 };
 
-function invokeLoadBootResource(request: ResourceRequest): string | Promise<Response> | null | undefined {
+function invokeLoadBootResource(asset: AssetEntryInternal): string | Promise<Response> | null | undefined {
     if (loaderHelpers.loadBootResource) {
-        const requestHash = request.hash ?? "";
-        const url = request.resolvedUrl!;
+        const requestHash = asset.hash ?? "";
+        const url = asset.resolvedUrl!;
 
-        const resourceType = monoToBlazorAssetTypeMap[request.behavior];
+        const resourceType = monoToBlazorAssetTypeMap[asset.behavior];
         if (resourceType) {
-            return loaderHelpers.loadBootResource(resourceType, request.name, url, requestHash, request.behavior);
+            const customLoadResult = loaderHelpers.loadBootResource(resourceType, asset.name, url, requestHash, asset.behavior);
+            if (typeof customLoadResult === "string") {
+                return makeURLAbsoluteWithApplicationBase(customLoadResult);
+            }
+            return customLoadResult;
         }
     }
 
index b9c44f3..0990ba2 100644 (file)
@@ -1,19 +1,15 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-import type { AssetBehaviors, MonoConfig, ResourceRequest } from "../types";
-import { loaderHelpers } from "./globals";
+import type { MonoConfig } from "../types";
+import type { AssetEntryInternal } from "../types/internal";
+import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals";
 
-const cacheSkipAssetBehaviors: AssetBehaviors[] = ["vfs"]; // Previously only configuration
 const usedCacheKeys: { [key: string]: boolean } = {};
 const networkLoads: { [name: string]: LoadLogEntry } = {};
 const cacheLoads: { [name: string]: LoadLogEntry } = {};
 let cacheIfUsed: Cache | null;
 
-export function isCacheAvailable(): boolean {
-    return !!cacheIfUsed;
-}
-
 export function logDownloadStatsToConsole(): void {
     const cacheLoadsEntries = Object.values(cacheLoads);
     const networkLoadsEntries = Object.values(networkLoads);
@@ -24,10 +20,14 @@ export function logDownloadStatsToConsole(): void {
         // We have no perf stats to display, likely because caching is not in use.
         return;
     }
-
-    const linkerDisabledWarning = loaderHelpers.config.linkerEnabled ? "%c" : "\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller if you install wasm-tools workload. See also https://aka.ms/dotnet-wasm-features";
+    const useStyle = ENVIRONMENT_IS_WEB ? "%c" : "";
+    const style = ENVIRONMENT_IS_WEB ? ["background: purple; color: white; padding: 1px 3px; border-radius: 3px;",
+        "font-weight: bold;",
+        "font-weight: normal;",
+    ] : [];
+    const linkerDisabledWarning = !loaderHelpers.config.linkerEnabled ? "\nThis application was built with linking (tree shaking) disabled. \nPublished applications will be significantly smaller if you install wasm-tools workload. \nSee also https://aka.ms/dotnet-wasm-features" : "";
     // eslint-disable-next-line no-console
-    console.groupCollapsed(`%cdotnet%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, "background: purple; color: white; padding: 1px 3px; border-radius: 3px;", "font-weight: bold;", "font-weight: normal;");
+    console.groupCollapsed(`${useStyle}dotnet${useStyle} Loaded ${toDataSizeString(totalResponseBytes)} resources${useStyle}${linkerDisabledWarning}`, ...style);
 
     if (cacheLoadsEntries.length) {
         // eslint-disable-next-line no-console
@@ -67,13 +67,13 @@ export async function purgeUnusedCacheEntriesAsync(): Promise<void> {
     }
 }
 
-export async function findCachedResponse(request: ResourceRequest): Promise<Response | undefined> {
+export async function findCachedResponse(asset: AssetEntryInternal): Promise<Response | undefined> {
     const cache = cacheIfUsed;
-    if (!cache || cacheSkipAssetBehaviors.includes(request.behavior) || !request.hash || request.hash.length === 0) {
+    if (!cache || asset.noCache || !asset.hash || asset.hash.length === 0) {
         return undefined;
     }
 
-    const cacheKey = getCacheKey(request);
+    const cacheKey = getCacheKey(asset);
     usedCacheKeys[cacheKey] = true;
 
     let cachedResponse: Response | undefined;
@@ -90,34 +90,38 @@ export async function findCachedResponse(request: ResourceRequest): Promise<Resp
 
     // It's in the cache.
     const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0");
-    cacheLoads[request.name] = { responseBytes };
+    cacheLoads[asset.name] = { responseBytes };
     return cachedResponse;
 }
 
-export function addCachedReponse(request: ResourceRequest, networkResponse: Response): void {
+export function addCachedReponse(asset: AssetEntryInternal, networkResponse: Response): void {
     const cache = cacheIfUsed;
-    if (!cache || cacheSkipAssetBehaviors.includes(request.behavior) || !request.hash || request.hash.length === 0) {
+    if (!cache || asset.noCache || !asset.hash || asset.hash.length === 0) {
         return;
     }
+    const clonedResponse = networkResponse.clone();
 
-    const cacheKey = getCacheKey(request);
-    addToCacheAsync(cache, request.name, cacheKey, networkResponse); // Don't await - add to cache in background
+    // postpone adding to cache until after we load the assembly, so that we could do the dotnet loading of the asset first
+    setTimeout(() => {
+        const cacheKey = getCacheKey(asset);
+        addToCacheAsync(cache, asset.name, cacheKey, clonedResponse); // Don't await - add to cache in background
+    }, 0);
 }
 
-function getCacheKey(request: ResourceRequest) {
-    return `${request.resolvedUrl}.${request.hash}`;
+function getCacheKey(asset: AssetEntryInternal) {
+    return `${asset.resolvedUrl}.${asset.hash}`;
 }
 
-async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) {
+async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, clonedResponse: Response) {
     // We have to clone in order to put this in the cache *and* not prevent other code from
     // reading the original response stream.
-    const responseData = await response.clone().arrayBuffer();
+    const responseData = await clonedResponse.arrayBuffer();
 
     // Now is an ideal moment to capture the performance stats for the request, since it
     // only just completed and is most likely to still be in the buffer. However this is
     // only done on a 'best effort' basis. Even if we do receive an entry, some of its
     // properties may be blanked out if it was a CORS request.
-    const performanceEntry = getPerformanceEntry(response.url);
+    const performanceEntry = getPerformanceEntry(clonedResponse.url);
     const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined;
     networkLoads[name] = { responseBytes };
 
@@ -125,8 +129,8 @@ async function addToCacheAsync(cache: Cache, name: string, cacheKey: string, res
     // We can't rely on the server sending content-length (ASP.NET Core doesn't by default)
     const responseToCache = new Response(responseData, {
         headers: {
-            "content-type": response.headers.get("content-type") || "",
-            "content-length": (responseBytes || response.headers.get("content-length") || "").toString(),
+            "content-type": clonedResponse.headers.get("content-type") || "",
+            "content-length": (responseBytes || clonedResponse.headers.get("content-length") || "").toString(),
         },
     });
 
index dd24733..053d769 100644 (file)
@@ -6,9 +6,10 @@ import type { DotnetModuleInternal, MonoConfigInternal } from "../types/internal
 import type { DotnetModuleConfig, MonoConfig, ResourceGroups, ResourceList } from "../types";
 import { ENVIRONMENT_IS_WEB, exportedRuntimeAPI, loaderHelpers, runtimeHelpers } from "./globals";
 import { mono_log_error, mono_log_debug } from "./logging";
-import { invokeLibraryInitializers } from "./libraryInitializers";
+import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers";
 import { mono_exit } from "./exit";
 import { makeURLAbsoluteWithApplicationBase } from "./polyfills";
+import { appendUniqueQuery } from "./assets";
 
 export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal {
     // no need to merge the same object
@@ -227,8 +228,6 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi
 
         normalizeConfig();
 
-        await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config], "modulesAfterConfigLoaded");
-
         if (module.onConfigLoaded) {
             try {
                 await module.onConfigLoaded(loaderHelpers.config, exportedRuntimeAPI);
@@ -239,6 +238,11 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi
                 throw err;
             }
         }
+
+        await importLibraryInitializers(loaderHelpers.config.resources?.modulesAfterConfigLoaded);
+        await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config]);
+        normalizeConfig();
+
         loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config);
     } catch (err) {
         const errMessage = `Failed to load config file ${configFilePath} ${err} ${(err as Error)?.stack}`;
@@ -268,7 +272,7 @@ async function loadBootConfig(module: DotnetModuleInternal): Promise<void> {
     let loadConfigResponse: Response;
 
     if (!loaderResponse) {
-        loadConfigResponse = await defaultLoadBootConfig(defaultConfigSrc);
+        loadConfigResponse = await defaultLoadBootConfig(appendUniqueQuery(defaultConfigSrc, "manifest"));
     } else if (typeof loaderResponse === "string") {
         loadConfigResponse = await defaultLoadBootConfig(makeURLAbsoluteWithApplicationBase(loaderResponse));
     } else {
index 7b2225f..09c7cad 100644 (file)
@@ -74,6 +74,7 @@ export function setLoaderGlobals(
         _loaded_files: [],
         loadedFiles: [],
         loadedAssemblies: [],
+        libraryInitializers: [],
         actual_downloaded_assets_count: 0,
         actual_instantiated_assets_count: 0,
         expected_downloaded_assets_count: 0,
@@ -92,7 +93,7 @@ export function setLoaderGlobals(
         getPromiseController,
         assertIsControllablePromise,
         mono_download_assets,
-        resolve_asset_path: resolve_single_asset_path,
+        resolve_single_asset_path,
         setup_proxy_console,
         logDownloadStatsToConsole,
         purgeUnusedCacheEntriesAsync,
index 999d847..48fd652 100644 (file)
@@ -1,25 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-import { mono_log_warn } from "./logging";
-import { MonoConfig } from "../types";
+import { mono_log_debug, mono_log_warn } from "./logging";
 import { appendUniqueQuery } from "./assets";
 import { loaderHelpers } from "./globals";
 import { mono_exit } from "./exit";
+import { ResourceList } from "../types";
 
-export type LibraryInitializerTypes =
-    "modulesAfterConfigLoaded"
-    | "modulesAfterRuntimeReady";
-
-async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitializerTypes): Promise<void> {
-    if (!loaderHelpers.libraryInitializers) {
-        loaderHelpers.libraryInitializers = [];
-    }
-
-    const libraryInitializers = type == "modulesAfterConfigLoaded"
-        ? config.resources?.modulesAfterConfigLoaded
-        : config.resources?.modulesAfterRuntimeReady;
-
+export async function importLibraryInitializers(libraryInitializers: ResourceList | undefined): Promise<void> {
     if (!libraryInitializers) {
         return;
     }
@@ -30,6 +18,7 @@ async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitial
     async function importInitializer(path: string): Promise<void> {
         try {
             const adjustedPath = appendUniqueQuery(loaderHelpers.locateFile(path), "js-module-library-initializer");
+            mono_log_debug(`Attempting to import '${adjustedPath}' for ${path}`);
             const initializer = await import(/* webpackIgnore: true */ adjustedPath);
 
             loaderHelpers.libraryInitializers!.push({ scriptName: path, exports: initializer });
@@ -39,11 +28,7 @@ async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitial
     }
 }
 
-export async function invokeLibraryInitializers(functionName: string, args: any[], type?: LibraryInitializerTypes) {
-    if (type) {
-        await fetchLibraryInitializers(loaderHelpers.config, type);
-    }
-
+export async function invokeLibraryInitializers(functionName: string, args: any[]) {
     if (!loaderHelpers.libraryInitializers) {
         return;
     }
index 1c0e545..e6bd201 100644 (file)
@@ -9,13 +9,13 @@ import type { MonoConfigInternal, EmscriptenModuleInternal, RuntimeModuleExports
 import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, exportedRuntimeAPI, globalObjectsRoot, mono_assert } from "./globals";
 import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config";
 import { mono_exit } from "./exit";
-import { setup_proxy_console, mono_log_info } from "./logging";
-import { resolve_single_asset_path, start_asset_download } from "./assets";
+import { setup_proxy_console, mono_log_info, mono_log_debug } from "./logging";
+import { mono_download_assets, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, start_asset_download } from "./assets";
 import { detect_features_and_polyfill } from "./polyfills";
 import { runtimeHelpers, loaderHelpers } from "./globals";
 import { init_globalization } from "./icu";
 import { setupPreloadChannelToMainThread } from "./worker";
-import { invokeLibraryInitializers } from "./libraryInitializers";
+import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers";
 import { initCacheToUseIfEnabled } from "./assetsCache";
 
 const module = globalObjectsRoot.module;
@@ -429,12 +429,14 @@ export async function createEmscripten(moduleFactory: DotnetModuleConfig | ((api
 }
 
 function importModules() {
-    runtimeHelpers.runtimeModuleUrl = resolve_single_asset_path("js-module-runtime").resolvedUrl!;
-    runtimeHelpers.nativeModuleUrl = resolve_single_asset_path("js-module-native").resolvedUrl!;
+    const jsModuleRuntimeAsset = resolve_single_asset_path("js-module-runtime");
+    const jsModuleNativeAsset = resolve_single_asset_path("js-module-native");
+    mono_log_debug(`Attempting to import '${jsModuleRuntimeAsset.resolvedUrl}' for ${jsModuleRuntimeAsset.name}`);
+    mono_log_debug(`Attempting to import '${jsModuleNativeAsset.resolvedUrl}' for ${jsModuleNativeAsset.name}`);
     return [
         // keep js module names dynamic by using config, in the future we can use feature detection to load different flavors
-        import(/* webpackIgnore: true */runtimeHelpers.runtimeModuleUrl),
-        import(/* webpackIgnore: true */runtimeHelpers.nativeModuleUrl),
+        import(/* webpackIgnore: true */jsModuleRuntimeAsset.resolvedUrl!),
+        import(/* webpackIgnore: true */jsModuleNativeAsset.resolvedUrl!),
     ];
 }
 
@@ -466,6 +468,8 @@ async function createEmscriptenMain(): Promise<RuntimeAPI> {
     // download config
     await mono_wasm_load_config(module);
 
+    prepareAssets();
+
     const promises = importModules();
 
     await initCacheToUseIfEnabled();
@@ -479,13 +483,18 @@ async function createEmscriptenMain(): Promise<RuntimeAPI> {
 
     init_globalization();
 
-    // TODO call mono_download_assets(); here in parallel ?
+    setTimeout(() => {
+        mono_download_assets(); // intentionally not awaited
+    }, 0);
+
     const es6Modules = await Promise.all(promises);
+
     await initializeModules(es6Modules as any);
 
     await runtimeHelpers.dotnetReady.promise;
 
-    await invokeLibraryInitializers("onRuntimeReady", [globalObjectsRoot.api], "modulesAfterRuntimeReady");
+    await importLibraryInitializers(loaderHelpers.config.resources?.modulesAfterRuntimeReady);
+    await invokeLibraryInitializers("onRuntimeReady", [globalObjectsRoot.api]);
 
     return exportedRuntimeAPI;
 }
@@ -495,6 +504,8 @@ async function createEmscriptenWorker(): Promise<EmscriptenModuleInternal> {
 
     await loaderHelpers.afterConfigLoaded.promise;
 
+    prepareAssetsWorker();
+
     const promises = importModules();
     const es6Modules = await Promise.all(promises);
     await initializeModules(es6Modules as any);
index 6487699..9b5b9fa 100644 (file)
@@ -35,7 +35,7 @@ export function replaceEmscriptenPThreadLibrary(replacements: PThreadReplacement
 /// We replace Module["PThreads"].allocateUnusedWorker with this version that knows about assets
 function replacementAllocateUnusedWorker(): void {
     mono_log_debug("replacementAllocateUnusedWorker");
-    const asset = loaderHelpers.resolve_asset_path("js-module-threads");
+    const asset = loaderHelpers.resolve_single_asset_path("js-module-threads");
     const uri = asset.resolvedUrl;
     mono_assert(uri !== undefined, "could not resolve the uri for the js-module-threads asset");
     const worker = new Worker(uri);
index 3ea00a8..3cc7317 100644 (file)
@@ -167,9 +167,6 @@ function preInit(userPreInit: (() => void)[]) {
             // - init the rest of the polyfills
             await mono_wasm_pre_init_essential_async();
 
-            // - start download assets like DLLs
-            await mono_wasm_pre_init_full();
-
             endMeasure(mark, MeasuredBlock.preInit);
         } catch (err) {
             loaderHelpers.mono_exit(1, err);
@@ -272,7 +269,10 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) {
         if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) {
             loaderHelpers.logDownloadStatsToConsole();
         }
-        loaderHelpers.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background
+        const afterStartupRushIsOver = 10000;// 10 seconds
+        setTimeout(() => {
+            loaderHelpers.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background
+        }, afterStartupRushIsOver);
 
         // call user code
         try {
@@ -382,15 +382,6 @@ async function mono_wasm_pre_init_essential_async(): Promise<void> {
     Module.removeRunDependency("mono_wasm_pre_init_essential_async");
 }
 
-async function mono_wasm_pre_init_full(): Promise<void> {
-    mono_log_debug("mono_wasm_pre_init_full");
-    Module.addRunDependency("mono_wasm_pre_init_full");
-
-    await loaderHelpers.mono_download_assets();
-
-    Module.removeRunDependency("mono_wasm_pre_init_full");
-}
-
 async function mono_wasm_after_user_runtime_initialized(): Promise<void> {
     mono_log_debug("mono_wasm_after_user_runtime_initialized");
     try {
index 857c953..cdd34f3 100644 (file)
@@ -2,7 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 import type { IMemoryView } from "../marshal";
-import type { CreateDotnetRuntimeType, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI, AssetEntry, ResourceRequest, GlobalizationMode, AssetBehaviors } from ".";
+import type { CreateDotnetRuntimeType, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI, AssetEntry, GlobalizationMode, AssetBehaviors } from ".";
 import type { EmscriptenModule } from "./emscripten";
 import type { dotnet, exit } from "../loader/index";
 
@@ -21,6 +21,6 @@ export default createDotnetRuntime;
 
 export {
     EmscriptenModule,
-    RuntimeAPI, ModuleAPI, DotnetHostBuilder, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, ResourceRequest, GlobalizationMode, AssetBehaviors,
+    RuntimeAPI, ModuleAPI, DotnetHostBuilder, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, GlobalizationMode, AssetBehaviors,
     dotnet, exit
 };
index c6001ee..7be774e 100644 (file)
@@ -64,6 +64,14 @@ export type MonoConfig = {
      */
     cacheBootResources?: boolean,
     /**
+     * Configures use of the `integrity` directive for fetching assets
+     */
+    disableIntegrityCheck?: boolean,
+    /**
+     * Configures use of the `no-cache` directive for fetching assets
+     */
+    disableNoCacheFetch?: boolean,
+    /**
     * Enables diagnostic log messages during startup
     */
     diagnosticTracing?: boolean
@@ -149,13 +157,6 @@ export type ResourceList = { [name: string]: string | null | "" };
  */
 export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string, behavior: AssetBehaviors) => string | Promise<Response> | null | undefined;
 
-export interface ResourceRequest {
-    name: string, // the name of the asset, including extension.
-    behavior: AssetBehaviors, // determines how the asset will be handled once loaded
-    resolvedUrl?: string; // this should be absolute url to the asset
-    hash?: string | null | ""; // the integrity hash of the asset (if any)
-}
-
 export interface LoadingResource {
     name: string;
     url: string;
@@ -163,7 +164,23 @@ export interface LoadingResource {
 }
 
 // Types of assets that can be in the _framework/blazor.boot.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs)
-export interface AssetEntry extends ResourceRequest {
+export interface AssetEntry {
+    /**
+     * the name of the asset, including extension.
+     */
+    name: string,
+    /**
+     * determines how the asset will be handled once loaded
+     */
+    behavior: AssetBehaviors,
+    /**
+     * this should be absolute url to the asset
+     */
+    resolvedUrl?: string;
+    /**
+     * the integrity hash of the asset (if any)
+     */
+    hash?: string | null | ""; // 
     /**
      * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded.
      */
index dda104d..3e218a4 100644 (file)
@@ -69,7 +69,7 @@ export function coerceNull<T extends ManagedPointer | NativePointer>(ptr: T | nu
 // when adding new fields, please consider if it should be impacting the snapshot hash. If not, please drop it in the snapshot getCacheKey()
 export type MonoConfigInternal = MonoConfig & {
     linkerEnabled?: boolean,
-    assets?: AssetEntry[],
+    assets?: AssetEntryInternal[],
     runtimeOptions?: string[], // array of runtime options as strings
     aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized.
     browserProfilerOptions?: BrowserProfilerOptions, // dictionary-style Object. If omitted, browser profiler will not be initialized.
@@ -93,8 +93,10 @@ export type RunArguments = {
 }
 
 export interface AssetEntryInternal extends AssetEntry {
-    // this is almost the same as pendingDownload, but it could have multiple values in time, because of re-try download logic
+    // this could have multiple values in time, because of re-try download logic
     pendingDownloadInternal?: LoadingResource
+    noCache?: boolean
+    useCredentials?: boolean
 }
 
 export type LoaderHelpers = {
@@ -134,7 +136,7 @@ export type LoaderHelpers = {
     getPromiseController: <T>(promise: ControllablePromise<T>) => PromiseController<T>,
     assertIsControllablePromise: <T>(promise: Promise<T>) => asserts promise is ControllablePromise<T>,
     mono_download_assets: () => Promise<void>,
-    resolve_asset_path: (behavior: AssetBehaviors) => AssetEntryInternal,
+    resolve_single_asset_path: (behavior: AssetBehaviors) => AssetEntryInternal,
     setup_proxy_console: (id: string, console: Console, origin: string) => void
     fetch_like: (url: string, init?: RequestInit) => Promise<Response>;
     locateFile: (path: string, prefix?: string) => string,
@@ -187,8 +189,6 @@ export type RuntimeHelpers = {
     jsSynchronizationContextInstalled: boolean,
     cspPolicy: boolean,
 
-    runtimeModuleUrl: string
-    nativeModuleUrl: string
     allAssetsInMemory: PromiseAndController<void>,
     dotnetReady: PromiseAndController<any>,
     memorySnapshotSkippedOrDone: PromiseAndController<void>,