From 82b1279e89624e5859f3f7a4e90233f4cec2773b Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 6 Feb 2023 13:04:44 +0100 Subject: [PATCH 1/5] JSImport --- src/Components/Web.JS/src/GlobalExports.ts | 3 +- .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 45 ++++++-------- .../src/Hosting/WebAssemblyCultureProvider.cs | 58 +++++++++---------- .../Hosting/WebAssemblyCultureProviderTest.cs | 51 ---------------- 4 files changed, 46 insertions(+), 111 deletions(-) diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 4fca849b69ff..360cdc68c08d 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -55,10 +55,9 @@ interface IBlazor { getApplicationEnvironment?: () => System_String, readLazyAssemblies?: () => System_Array, readLazyPdbs?: () => System_Array, - readSatelliteAssemblies?: () => System_Array, getLazyAssemblies?: any dotNetCriticalError?: 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 be4fecb69f7b..797285f5dc00 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -344,33 +344,7 @@ 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; const lazyResources: { assemblies?: (ArrayBuffer | null)[], @@ -443,6 +417,23 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc }; }; + 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); + })); + } + const postRun = () => { if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) { resourceLoader.logToConsole(); diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs index 331cbb8e7ab6..02c5a0ec81c3 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) + { + byte[] 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,14 @@ 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() { From faf6a1d5851564228c67a465803f9ce3bb782ea5 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 7 Feb 2023 09:47:05 +0100 Subject: [PATCH 2/5] Update src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs Co-authored-by: Mackinnon Buck --- .../WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs index 02c5a0ec81c3..5a40cf6be733 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs @@ -119,5 +119,4 @@ private partial class WebAssemblyCultureProviderInterop public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad, [JSMarshalAs>] Action assemblyLoader); } - } From 7c9c46158e3b6d0403b8a30412b3490607a0b1e3 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 7 Feb 2023 10:40:58 +0100 Subject: [PATCH 3/5] Update src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs Co-authored-by: Steve Sanderson --- .../WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs index 5a40cf6be733..cdbdb9be7e3e 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs @@ -83,7 +83,7 @@ public virtual async ValueTask LoadCurrentCultureResourcesAsync() [SupportedOSPlatform("browser")] private void LoadSatelliteAssembly(JSObject wrapper) { - byte[] dllBytes = wrapper.GetPropertyAsByteArray("dll")!; + var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; using var stream = new MemoryStream(dllBytes); AssemblyLoadContext.Default.LoadFromStream(stream); wrapper.Dispose(); From 2dcb6fda375431b26d023b33319aea4929411c36 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 7 Feb 2023 13:01:33 +0100 Subject: [PATCH 4/5] fix --- src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index d327c466984f..dd673e84785d 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -12,7 +12,7 @@ import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions'; import { BootJsonData, ICUDataMode } from '../BootConfig'; import { Blazor } from '../../GlobalExports'; -import { RuntimeAPI, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, AssetEntry, ResourceRequest } from 'dotnet'; +import { RuntimeAPI, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, AssetEntry } from 'dotnet'; import { BINDINGType, MONOType } from 'dotnet/dotnet-legacy'; // initially undefined and only fully initialized after createEmscriptenModuleInstance() From 8dca4e3ab19dbe17c40519d3a912f85ba6654765 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 7 Feb 2023 13:03:52 +0100 Subject: [PATCH 5/5] fix merge --- .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index dd673e84785d..34aeea34be59 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -225,7 +225,7 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc // If anything writes to stderr, treat it as a critical exception. The underlying runtime writes // to stderr if a truly critical problem occurs outside .NET code. Note that .NET unhandled // exceptions also reach this, but via a different code path - see dotNetCriticalError below. - console.error(line); + console.error(line || '(null)'); showErrorNotification(); }; const existingPreRun = moduleConfig.preRun || [] as any; @@ -234,11 +234,11 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc let resourcesLoaded = 0; function setProgress(){ - resourcesLoaded++; - const percentage = resourcesLoaded / totalResources.length * 100; - document.documentElement.style.setProperty('--blazor-load-percentage', `${percentage}%`); - document.documentElement.style.setProperty('--blazor-load-percentage-text', `"${Math.floor(percentage)}%"`); - } + resourcesLoaded++; + const percentage = resourcesLoaded / totalResources.length * 100; + document.documentElement.style.setProperty('--blazor-load-percentage', `${percentage}%`); + document.documentElement.style.setProperty('--blazor-load-percentage-text', `"${Math.floor(percentage)}%"`); + } const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { 'assembly': 'assembly',