diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index d36952a58d35..2e2075ed321f 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -53,10 +53,9 @@ interface IBlazor { renderBatch?: (browserRendererId: number, batchAddress: Pointer) => void, getConfig?: (dotNetFileName: System_String) => System_Object | undefined, getApplicationEnvironment?: () => System_String, - readSatelliteAssemblies?: () => System_Array, dotNetCriticalError?: any loadLazyAssembly?: any, - getSatelliteAssemblies?: any, + loadSatelliteAssemblies?: any, sendJSDataStream?: (data: any, streamId: number, chunkSize: number) => void, getJSDataStreamChunk?: (data: any, position: number, chunkSize: number) => Promise, receiveDotNetDataStream?: (streamId: number, data: any, bytesRead: number, errorMessage: string) => void, diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 14161f5b34a2..34aeea34be59 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -233,7 +233,7 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc (moduleConfig as any).preloadPlugins = []; let resourcesLoaded = 0; - function setProgress() { + function setProgress(){ resourcesLoaded++; const percentage = resourcesLoaded / totalResources.length * 100; document.documentElement.style.setProperty('--blazor-load-percentage', `${percentage}%`); @@ -347,36 +347,26 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc // Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application // startup sequence to load satellite assemblies for the application's culture. - Blazor._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray) => { - const culturesToLoad = BINDING.mono_array_to_js_array(culturesToLoadDotNetArray); - const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; - - if (satelliteResources) { - const resourcePromises = Promise.all(culturesToLoad! - .filter(culture => satelliteResources.hasOwnProperty(culture)) - .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/${fileName}`, 'assembly')) - .reduce((previous, next) => previous.concat(next), new Array()) - .map(async resource => (await resource.response).arrayBuffer())); - - return BINDING.js_to_mono_obj(resourcePromises.then(resourcesToLoad => { - if (resourcesToLoad.length) { - Blazor._internal.readSatelliteAssemblies = () => { - const array = BINDING.mono_obj_array_new(resourcesToLoad.length); - for (let i = 0; i < resourcesToLoad.length; i++) { - BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i]))); - } - return array as any; - }; - } - - return resourcesToLoad.length; - })); - } - return BINDING.js_to_mono_obj(Promise.resolve(0)); - }; - + Blazor._internal.loadSatelliteAssemblies = loadSatelliteAssemblies; }; + async function loadSatelliteAssemblies(culturesToLoad: string[], loader: (wrapper: { dll: Uint8Array }) => void): Promise { + const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; + if (!satelliteResources) { + return; + } + await Promise.all(culturesToLoad! + .filter(culture => satelliteResources.hasOwnProperty(culture)) + .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/${fileName}`, 'assembly')) + .reduce((previous, next) => previous.concat(next), new Array()) + .map(async resource => { + const response = await resource.response; + const bytes = await response.arrayBuffer(); + const wrapper = { dll: new Uint8Array(bytes) }; + loader(wrapper); + })); + } + async function loadLazyAssembly(assemblyNameToLoad: string): Promise<{ dll: Uint8Array, pdb: Uint8Array | null }> { const lazyAssemblies = resources.lazyAssembly; if (!lazyAssemblies) { diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs index 331cbb8e7ab6..cdbdb9be7e3e 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs @@ -3,7 +3,9 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.InteropServices.JavaScript; using System.Runtime.Loader; +using System.Runtime.Versioning; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.JSInterop; @@ -11,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting; [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "This type loads resx files. We don't expect it's dependencies to be trimmed in the ordinary case.")] #pragma warning disable CA1852 // Seal internal types -internal class WebAssemblyCultureProvider +internal partial class WebAssemblyCultureProvider #pragma warning restore CA1852 // Seal internal types { internal const string GetSatelliteAssemblies = "window.Blazor._internal.getSatelliteAssemblies"; @@ -63,45 +65,31 @@ public void ThrowIfCultureChangeIsUnsupported() public virtual async ValueTask LoadCurrentCultureResourcesAsync() { - var culturesToLoad = GetCultures(CultureInfo.CurrentCulture); - - if (culturesToLoad.Count == 0) + if (!OperatingSystem.IsBrowser()) { - return; + throw new PlatformNotSupportedException("This method is only supported in the browser."); } - // Now that we know the cultures we care about, let WebAssemblyResourceLoader (in JavaScript) load these - // assemblies. We effectively want to resovle a Task but there is no way to express this - // using interop. We'll instead do this in two parts: - // getSatelliteAssemblies resolves when all satellite assemblies to be loaded in .NET are fetched and available in memory. -#pragma warning disable CS0618 // Type or member is obsolete - var count = (int)await _invoker.InvokeUnmarshalled>( - GetSatelliteAssemblies, - culturesToLoad.ToArray(), - null, - null); - - if (count == 0) + var culturesToLoad = GetCultures(CultureInfo.CurrentCulture); + + if (culturesToLoad.Length == 0) { return; } - // readSatelliteAssemblies resolves the assembly bytes - var assemblies = _invoker.InvokeUnmarshalled( - ReadSatelliteAssemblies, - null, - null, - null); -#pragma warning restore CS0618 // Type or member is obsolete + await WebAssemblyCultureProviderInterop.LoadSatelliteAssemblies(culturesToLoad, LoadSatelliteAssembly); + } - for (var i = 0; i < assemblies.Length; i++) - { - using var stream = new MemoryStream((byte[])assemblies[i]); - AssemblyLoadContext.Default.LoadFromStream(stream); - } + [SupportedOSPlatform("browser")] + private void LoadSatelliteAssembly(JSObject wrapper) + { + var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; + using var stream = new MemoryStream(dllBytes); + AssemblyLoadContext.Default.LoadFromStream(stream); + wrapper.Dispose(); } - internal static List GetCultures(CultureInfo cultureInfo) + internal static string[] GetCultures(CultureInfo cultureInfo) { var culturesToLoad = new List(); @@ -122,6 +110,13 @@ internal static List GetCultures(CultureInfo cultureInfo) cultureInfo = cultureInfo.Parent; } - return culturesToLoad; + return culturesToLoad.ToArray(); + } + + private partial class WebAssemblyCultureProviderInterop + { + [JSImport("Blazor._internal.loadSatelliteAssemblies", "blazor-internal")] + public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad, + [JSMarshalAs>] Action assemblyLoader); } } diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyCultureProviderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyCultureProviderTest.cs index 9f182ef17a1b..50d798e01cf3 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyCultureProviderTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyCultureProviderTest.cs @@ -4,9 +4,6 @@ using System.Globalization; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.AspNetCore.Testing; -using Microsoft.JSInterop; -using Moq; -using static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyCultureProvider; namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -27,54 +24,6 @@ public void GetCultures_ReturnsCultureClosure(string cultureName, string[] expec Assert.Equal(expected, actual); } - [Fact] - public async Task LoadCurrentCultureResourcesAsync_ReadsAssemblies() - { - // Arrange - using var cultureReplacer = new CultureReplacer("en-GB"); - var invoker = new Mock(); -#pragma warning disable CS0618 // Type or member is obsolete - invoker.Setup(i => i.InvokeUnmarshalled>(GetSatelliteAssemblies, new[] { "en-GB", "en" }, null, null)) - .Returns(Task.FromResult(1)) - .Verifiable(); - - invoker.Setup(i => i.InvokeUnmarshalled(ReadSatelliteAssemblies, null, null, null)) - .Returns(new object[] { File.ReadAllBytes(GetType().Assembly.Location) }) - .Verifiable(); -#pragma warning restore CS0618 // Type or member is obsolete - - var loader = new WebAssemblyCultureProvider(invoker.Object, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture); - - // Act - await loader.LoadCurrentCultureResourcesAsync(); - - // Assert - invoker.Verify(); - } - - [Fact] - public async Task LoadCurrentCultureResourcesAsync_DoesNotReadAssembliesWhenThereAreNone() - { - // Arrange - using var cultureReplacer = new CultureReplacer("en-GB"); - var invoker = new Mock(); -#pragma warning disable CS0618 // Type or member is obsolete - invoker.Setup(i => i.InvokeUnmarshalled>(GetSatelliteAssemblies, new[] { "en-GB", "en" }, null, null)) - .Returns(Task.FromResult(0)) - .Verifiable(); -#pragma warning restore CS0618 // Type or member is obsolete - - var loader = new WebAssemblyCultureProvider(invoker.Object, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture); - - // Act - await loader.LoadCurrentCultureResourcesAsync(); - -#pragma warning disable CS0618 // Type or member is obsolete - // Assert - invoker.Verify(i => i.InvokeUnmarshalled(ReadSatelliteAssemblies, null, null, null), Times.Never()); -#pragma warning restore CS0618 // Type or member is obsolete - } - [Fact] public void ThrowIfCultureChangeIsUnsupported_ThrowsIfCulturesAreDifferentAndICUShardingIsUsed() {