diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 8fb189d748b390..5700961fd53581 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -166,14 +166,6 @@ type MonoConfig = { * debugLevel < 0 enables debugging and disables debug logging. */ debugLevel?: number; - /** - * Gets a value that determines whether to enable caching of the 'resources' inside a CacheStorage instance within the browser. - */ - cacheBootResources?: boolean; - /** - * Delay of the purge of the cached resources in milliseconds. Default is 10000 (10 seconds). - */ - cachedResourcesPurgeDelay?: number; /** * Configures use of the `integrity` directive for fetching assets */ diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index 2f05054130ca93..89842ad9c403ce 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -9,7 +9,6 @@ import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONM import { createPromiseController } from "./promise-controller"; import { mono_log_debug, mono_log_warn } from "./logging"; import { mono_exit } from "./exit"; -import { addCachedReponse, findCachedResponse } from "./assetsCache"; import { getIcuResourceName } from "./icu"; import { makeURLAbsoluteWithApplicationBase } from "./polyfills"; import { mono_log_info } from "./logging"; @@ -675,7 +674,7 @@ const totalResources = new Set(); function download_resource (asset: AssetEntryInternal): LoadingResource { try { mono_assert(asset.resolvedUrl, "Request's resolvedUrl must be set"); - const fetchResponse = download_resource_with_cache(asset); + const fetchResponse = fetchResource(asset); const response = { name: asset.name, url: asset.resolvedUrl, response: fetchResponse }; totalResources.add(asset.name!); @@ -708,16 +707,6 @@ function download_resource (asset: AssetEntryInternal): LoadingResource { } } -async function download_resource_with_cache (asset: AssetEntryInternal): Promise { - let response = await findCachedResponse(asset); - if (!response) { - response = await fetchResource(asset); - addCachedReponse(asset, response); - } - - return response; -} - function fetchResource (asset: AssetEntryInternal): Promise { // Allow developers to override how the resource is loaded let url = asset.resolvedUrl!; diff --git a/src/mono/browser/runtime/loader/assetsCache.ts b/src/mono/browser/runtime/loader/assetsCache.ts deleted file mode 100644 index 1be22cfdd1fde5..00000000000000 --- a/src/mono/browser/runtime/loader/assetsCache.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import type { MonoConfig } from "../types"; -import type { AssetEntryInternal } from "../types/internal"; -import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; - -const usedCacheKeys: { [key: string]: boolean } = {}; -const networkLoads: { [name: string]: LoadLogEntry } = {}; -const cacheLoads: { [name: string]: LoadLogEntry } = {}; -let cacheIfUsed: Cache | null; - -export function logDownloadStatsToConsole (): void { - const cacheLoadsEntries = Object.values(cacheLoads); - const networkLoadsEntries = Object.values(networkLoads); - const cacheResponseBytes = countTotalBytes(cacheLoadsEntries); - const networkResponseBytes = countTotalBytes(networkLoadsEntries); - const totalResponseBytes = cacheResponseBytes + networkResponseBytes; - if (totalResponseBytes === 0) { - // We have no perf stats to display, likely because caching is not in use. - return; - } - 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(`${useStyle}dotnet${useStyle} Loaded ${toDataSizeString(totalResponseBytes)} resources${useStyle}${linkerDisabledWarning}`, ...style); - - if (cacheLoadsEntries.length) { - // eslint-disable-next-line no-console - console.groupCollapsed(`Loaded ${toDataSizeString(cacheResponseBytes)} resources from cache`); - // eslint-disable-next-line no-console - console.table(cacheLoads); - // eslint-disable-next-line no-console - console.groupEnd(); - } - - if (networkLoadsEntries.length) { - // eslint-disable-next-line no-console - console.groupCollapsed(`Loaded ${toDataSizeString(networkResponseBytes)} resources from network`); - // eslint-disable-next-line no-console - console.table(networkLoads); - // eslint-disable-next-line no-console - console.groupEnd(); - } - - // eslint-disable-next-line no-console - console.groupEnd(); -} - -export async function purgeUnusedCacheEntriesAsync (): Promise { - // We want to keep the cache small because, even though the browser will evict entries if it - // gets too big, we don't want to be considered problematic by the end user viewing storage stats - const cache = cacheIfUsed; - if (cache) { - const cachedRequests = await cache.keys(); - const deletionPromises = cachedRequests.map(async cachedRequest => { - if (!(cachedRequest.url in usedCacheKeys)) { - await cache.delete(cachedRequest); - } - }); - - await Promise.all(deletionPromises); - } -} - -export async function findCachedResponse (asset: AssetEntryInternal): Promise { - const cache = cacheIfUsed; - if (!cache || asset.noCache || !asset.hash || asset.hash.length === 0) { - return undefined; - } - - const cacheKey = getCacheKey(asset); - usedCacheKeys[cacheKey] = true; - - let cachedResponse: Response | undefined; - try { - cachedResponse = await cache.match(cacheKey); - } catch { - // Be tolerant to errors reading from the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where - // chromium browsers may sometimes throw when working with the cache. - } - - if (!cachedResponse) { - return undefined; - } - - // It's in the cache. - const responseBytes = parseInt(cachedResponse.headers.get("content-length") || "0"); - cacheLoads[asset.name] = { responseBytes }; - return cachedResponse; -} - -export function addCachedReponse (asset: AssetEntryInternal, networkResponse: Response): void { - const cache = cacheIfUsed; - if (!cache || asset.noCache || !asset.hash || asset.hash.length === 0) { - return; - } - const clonedResponse = networkResponse.clone(); - - // 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 (asset: AssetEntryInternal) { - return `${asset.resolvedUrl}.${asset.hash}`; -} - -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 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(clonedResponse.url); - const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined; - networkLoads[name] = { responseBytes }; - - // Add to cache as a custom response object so we can track extra data such as responseBytes - // 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": clonedResponse.headers.get("content-type") || "", - "content-length": (responseBytes || clonedResponse.headers.get("content-length") || "").toString(), - }, - }); - - try { - await cache.put(cacheKey, responseToCache); - } catch { - // Be tolerant to errors writing to the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where - // chromium browsers may sometimes throw when performing cache operations. - } -} - -export async function initCacheToUseIfEnabled (): Promise { - cacheIfUsed = await getCacheToUseIfEnabled(loaderHelpers.config); -} - -async function getCacheToUseIfEnabled (config: MonoConfig): Promise { - // caches will be undefined if we're running on an insecure origin (secure means https or localhost) - if (!config.cacheBootResources || typeof globalThis.caches === "undefined" || typeof globalThis.document === "undefined") { - return null; - } - - // cache integrity is compromised if the first request has been served over http (except localhost) - // in this case, we want to disable caching and integrity validation - if (globalThis.isSecureContext === false) { - return null; - } - - // Define a separate cache for each base href, so we're isolated from any other - // Blazor application running on the same origin. We need this so that we're free - // to purge from the cache anything we're not using and don't let it keep growing, - // since we don't want to be worst offenders for space usage. - const relativeBaseHref = globalThis.document.baseURI.substring(globalThis.document.location.origin.length); - const cacheName = `dotnet-resources-${relativeBaseHref}`; - - try { - // There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when - // caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance. - // However, if the browser was launched with a --user-data-dir param that's "too long" in some sense, - // then even through the promise resolves as success, the value given is `undefined`. - // See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541 - // If we see this happening, return "null" to mean "proceed without caching". - return (await caches.open(cacheName)) || null; - } catch { - // There's no known scenario where we should get an exception here, but considering the - // Chromium bug above, let's tolerate it and treat as "proceed without caching". - return null; - } -} - -function countTotalBytes (loads: LoadLogEntry[]) { - return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0); -} - -function toDataSizeString (byteCount: number) { - return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`; -} - -function getPerformanceEntry (url: string): PerformanceResourceTiming | undefined { - if (typeof performance !== "undefined") { - return performance.getEntriesByName(url)[0] as PerformanceResourceTiming; - } -} - -interface LoadLogEntry { - responseBytes: number | undefined; -} - -export interface LoadingResource { - name: string; - url: string; - response: Promise; -} diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 3ae87d5109b75d..b0cb66ad8d47d8 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -187,10 +187,6 @@ export function normalizeConfig () { config.debugLevel = -1; } - if (config.cachedResourcesPurgeDelay === undefined) { - config.cachedResourcesPurgeDelay = 10000; - } - if (!config.applicationEnvironment) { config.applicationEnvironment = "Production"; } diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index 249af3ae84315e..925474d4758853 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -16,7 +16,6 @@ import { mono_download_assets, resolve_single_asset_path, retrieve_asset_downloa import { mono_log_error, set_thread_prefix, setup_proxy_console } from "./logging"; import { invokeLibraryInitializers } from "./libraryInitializers"; import { deep_merge_config, isDebuggingSupported } from "./config"; -import { logDownloadStatsToConsole, purgeUnusedCacheEntriesAsync } from "./assetsCache"; // if we are the first script loaded in the web worker, we are expected to become the sidecar if (typeof importScripts === "function" && !globalThis.onmessage) { @@ -122,8 +121,6 @@ export function setLoaderGlobals ( resolve_single_asset_path, setup_proxy_console, set_thread_prefix, - logDownloadStatsToConsole, - purgeUnusedCacheEntriesAsync, installUnhandledErrorHandler, retrieve_asset_download, diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index b915ddc11ea834..5f8f428d4e19ed 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -16,7 +16,6 @@ import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers"; -import { initCacheToUseIfEnabled } from "./assetsCache"; export class HostBuilder implements DotnetHostBuilder { @@ -510,8 +509,6 @@ async function downloadOnly ():Promise { prepareAssets(); - await initCacheToUseIfEnabled(); - init_globalization(); mono_download_assets(); // intentionally not awaited @@ -527,8 +524,6 @@ async function createEmscriptenMain (): Promise { const promises = importModules(); - await initCacheToUseIfEnabled(); - streamingCompileWasm(); // intentionally not awaited setTimeout(async () => { diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 65e414f6c4af1f..083071124f6d2c 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -339,14 +339,6 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: (module:Emsc if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); - if (loaderHelpers.config.debugLevel !== 0 && loaderHelpers.config.cacheBootResources) { - loaderHelpers.logDownloadStatsToConsole(); - } - - setTimeout(() => { - loaderHelpers.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background - }, loaderHelpers.config.cachedResourcesPurgeDelay); - // call user code try { userOnRuntimeInitialized(Module); diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index da3394faecd7cc..e521025b9a0133 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -118,14 +118,6 @@ export type MonoConfig = { */ debugLevel?: number, - /** - * Gets a value that determines whether to enable caching of the 'resources' inside a CacheStorage instance within the browser. - */ - cacheBootResources?: boolean, - /** - * Delay of the purge of the cached resources in milliseconds. Default is 10000 (10 seconds). - */ - cachedResourcesPurgeDelay?: number, /** * Configures use of the `integrity` directive for fetching assets */ diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 81a4379be814b6..3133d7457ba18d 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -164,9 +164,7 @@ export type LoaderHelpers = { retrieve_asset_download(asset: AssetEntry): Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - logDownloadStatsToConsole: () => void; installUnhandledErrorHandler: () => void; - purgeUnusedCacheEntriesAsync: () => Promise; loadBootResource?: LoadBootResourceCallback; invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 9422801595deee..55403335852af1 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -64,7 +64,6 @@ Copyright (c) .NET Foundation. All rights reserved. true - true false @@ -176,6 +175,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_TargetingNET90OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '9.0'))">true <_TargetingNET100OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '10.0'))">true + <_BlazorCacheBootResources>$(BlazorCacheBootResources) + <_BlazorCacheBootResources Condition="'$(_BlazorCacheBootResources)' == '' and '$(_TargetingNET100OrLater)' != 'true'">true + <_BlazorCacheBootResources Condition="'$(_BlazorCacheBootResources)' == ''">false <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport) <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true <_WasmInvariantGlobalization>$(InvariantGlobalization) @@ -217,6 +219,7 @@ Copyright (c) .NET Foundation. All rights reserved. $(OutputPath)$(PublishDirName)\ + @@ -389,7 +392,7 @@ Copyright (c) .NET Foundation. All rights reserved. DebugBuild="true" DebugLevel="$(WasmDebugLevel)" LinkerEnabled="false" - CacheBootResources="$(BlazorCacheBootResources)" + CacheBootResources="$(_BlazorCacheBootResources)" MergeWith="@(_WasmDotnetJsForBuild)" OutputPath="$(_WasmBuildBootJsonPath)" ConfigurationFiles="@(_WasmJsConfigStaticWebAsset)" @@ -797,7 +800,7 @@ Copyright (c) .NET Foundation. All rights reserved. DebugBuild="false" DebugLevel="$(WasmDebugLevel)" LinkerEnabled="$(PublishTrimmed)" - CacheBootResources="$(BlazorCacheBootResources)" + CacheBootResources="$(_BlazorCacheBootResources)" MergeWith="@(_WasmDotnetJsForPublish)" OutputPath="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)" ConfigurationFiles="@(_WasmPublishConfigFile)" diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 1ed618e852c611..711c3d97c57f5f 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -127,8 +127,11 @@ private void WriteBootConfig(string entryAssemblyName) result.mainAssemblyName = entryAssemblyName; result.globalizationMode = GetGlobalizationMode().ToString().ToLowerInvariant(); - if (CacheBootResources) - result.cacheBootResources = CacheBootResources; + if (!IsTargeting100OrLater()) + { + if (CacheBootResources) + result.cacheBootResources = CacheBootResources; + } if (LinkerEnabled) result.linkerEnabled = LinkerEnabled;