Skip to content

Commit

Permalink
Eliminate framework use of 'IJSUnmarshalledRuntime' (#46693)
Browse files Browse the repository at this point in the history
Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
  • Loading branch information
MackinnonBuck and SteveSandersonMS committed Mar 7, 2023
1 parent 46a3d2b commit 8fbf7f2
Show file tree
Hide file tree
Showing 29 changed files with 413 additions and 307 deletions.
4 changes: 2 additions & 2 deletions src/Components/Shared/src/BrowserNavigationManagerInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ internal static class BrowserNavigationManagerInterop

public const string EnableNavigationInterception = Prefix + "enableNavigationInterception";

public const string GetLocationHref = Prefix + "getUnmarshalledLocationHref";
public const string GetLocationHref = Prefix + "getLocationHref";

public const string GetBaseUri = Prefix + "getUnmarshalledBaseURI";
public const string GetBaseUri = Prefix + "getBaseURI";

public const string NavigateTo = Prefix + "navigateTo";

Expand Down
49 changes: 19 additions & 30 deletions src/Components/Web.JS/src/Boot.WebAssembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import { DotNet } from '@microsoft/dotnet-js-interop';
import { Blazor } from './GlobalExports';
import * as Environment from './Environment';
import { byteArrayBeingTransferred, Module, BINDING, monoPlatform } from './Platform/Mono/MonoPlatform';
import { Module, BINDING, monoPlatform } from './Platform/Mono/MonoPlatform';
import { renderBatch, getRendererer, attachRootComponentToElement, attachRootComponentToLogicalElement } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { shouldAutoStart } from './BootCommon';
import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader';
import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
import { BootConfigResult } from './Platform/BootConfig';
import { Pointer, System_Array, System_Boolean, System_Byte, System_Int, System_Object, System_String } from './Platform/Platform';
import { Pointer } from './Platform/Platform';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
import { discoverComponents, discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
Expand Down Expand Up @@ -52,9 +52,9 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {

// Configure JS interop
Blazor._internal.invokeJSFromDotNet = invokeJSFromDotNet;
Blazor._internal.invokeJSJson = invokeJSJson;
Blazor._internal.endInvokeDotNetFromJS = endInvokeDotNetFromJS;
Blazor._internal.receiveByteArray = receiveByteArray;
Blazor._internal.retrieveByteArray = retrieveByteArray;

// Configure environment for execution under Mono WebAssembly with shared-memory rendering
const platform = Environment.setPlatform(monoPlatform);
Expand All @@ -73,12 +73,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
}
};

// Configure navigation via JS Interop
const getBaseUri = Blazor._internal.navigationManager.getBaseURI;
const getLocationHref = Blazor._internal.navigationManager.getLocationHref;
Blazor._internal.navigationManager.getUnmarshalledBaseURI = () => BINDING.js_string_to_mono_string(getBaseUri());
Blazor._internal.navigationManager.getUnmarshalledLocationHref = () => BINDING.js_string_to_mono_string(getLocationHref());

Blazor._internal.navigationManager.listenForNavigationEvents(async (uri: string, state: string | undefined, intercepted: boolean): Promise<void> => {
await DotNet.invokeMethodAsync(
'Microsoft.AspNetCore.Components.WebAssembly',
Expand Down Expand Up @@ -114,13 +108,13 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
Blazor._internal.registeredComponents = {
getRegisteredComponentsCount: () => componentAttacher.getCount(),
getId: (index) => componentAttacher.getId(index),
getAssembly: (id) => BINDING.js_string_to_mono_string(componentAttacher.getAssembly(id)),
getTypeName: (id) => BINDING.js_string_to_mono_string(componentAttacher.getTypeName(id)),
getParameterDefinitions: (id) => BINDING.js_string_to_mono_string(componentAttacher.getParameterDefinitions(id) || ''),
getParameterValues: (id) => BINDING.js_string_to_mono_string(componentAttacher.getParameterValues(id) || ''),
getAssembly: (id) => componentAttacher.getAssembly(id),
getTypeName: (id) => componentAttacher.getTypeName(id),
getParameterDefinitions: (id) => componentAttacher.getParameterDefinitions(id) || '',
getParameterValues: (id) => componentAttacher.getParameterValues(id) || '',
};

Blazor._internal.getPersistedState = () => BINDING.js_string_to_mono_string(discoverPersistedState(document) || '');
Blazor._internal.getPersistedState = () => discoverPersistedState(document) || '';

Blazor._internal.attachRootComponentToElement = (selector, componentId, rendererId: any) => {
const element = componentAttacher.resolveRegisteredElement(selector);
Expand Down Expand Up @@ -191,26 +185,21 @@ function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any):
}
}

function endInvokeDotNetFromJS(callId: System_String, success: System_Boolean, resultJsonOrErrorMessage: System_String): void {
const callIdString = BINDING.conv_string(callId)!;
const successBool = (success as any as number) !== 0;
const resultJsonOrErrorMessageString = BINDING.conv_string(resultJsonOrErrorMessage)!;
DotNet.jsCallDispatcher.endInvokeDotNetFromJS(callIdString, successBool, resultJsonOrErrorMessageString);
function invokeJSJson(identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number): string | null {
if (asyncHandle !== 0) {
DotNet.jsCallDispatcher.beginInvokeJSFromDotNet(asyncHandle, identifier, argsJson, resultType, targetInstanceId);
return null;
} else {
return DotNet.jsCallDispatcher.invokeJSFromDotNet(identifier, argsJson, resultType, targetInstanceId);
}
}

function receiveByteArray(id: System_Int, data: System_Array<System_Byte>): void {
const idLong = id as unknown as number;
const dataByteArray = monoPlatform.toUint8Array(data);
DotNet.jsCallDispatcher.receiveByteArray(idLong, dataByteArray);
function endInvokeDotNetFromJS(callId: string, success: boolean, resultJsonOrErrorMessage: string): void {
DotNet.jsCallDispatcher.endInvokeDotNetFromJS(callId, success, resultJsonOrErrorMessage);
}

function retrieveByteArray(): System_Object {
if (byteArrayBeingTransferred === null) {
throw new Error('Byte array not available for transfer');
}

const typedArray = BINDING.js_typed_array_to_array(byteArrayBeingTransferred);
return typedArray;
function receiveByteArray(id: number, data: Uint8Array): void {
DotNet.jsCallDispatcher.receiveByteArray(id, data);
}

function inAuthRedirectIframe(): boolean {
Expand Down
66 changes: 37 additions & 29 deletions src/Components/Web.JS/src/GlobalExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { NavigationLock } from './NavigationLock';
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
import { CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { Platform, Pointer, System_String, System_Array, System_Object, System_Boolean, System_Byte, System_Int } from './Platform/Platform';
import { Platform, Pointer } from './Platform/Platform';
import { getNextChunk, receiveDotNetDataStream } from './StreamingInterop';
import { RootComponentsFunctions } from './Rendering/JSRootComponents';
import { attachWebRendererInterop } from './Rendering/WebRendererInteropMethods';
Expand All @@ -29,41 +29,49 @@ interface IBlazor {
rootComponents: typeof RootComponentsFunctions;

_internal: {
navigationManager: typeof navigationManagerInternalFunctions | any,
domWrapper: typeof domFunctions,
Virtualize: typeof Virtualize,
PageTitle: typeof PageTitle,
navigationManager: typeof navigationManagerInternalFunctions | any;
domWrapper: typeof domFunctions;
Virtualize: typeof Virtualize;
PageTitle: typeof PageTitle;
forceCloseConnection?: () => Promise<void>;
InputFile?: typeof InputFile,
NavigationLock: typeof NavigationLock,
InputFile?: typeof InputFile;
NavigationLock: typeof NavigationLock;
invokeJSFromDotNet?: (callInfo: Pointer, arg0: any, arg1: any, arg2: any) => any;
endInvokeDotNetFromJS?: (callId: System_String, success: System_Boolean, resultJsonOrErrorMessage: System_String) => void;
receiveByteArray?: (id: System_Int, data: System_Array<System_Byte>) => void;
retrieveByteArray?: () => System_Object;
getPersistedState?: () => System_String;
invokeJSJson?: (identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number) => string | null;
endInvokeDotNetFromJS?: (callId: string, success: boolean, resultJsonOrErrorMessage: string) => void;
receiveByteArray?: (id: number, data: Uint8Array) => void;
getPersistedState?: () => string;
attachRootComponentToElement?: (arg0: any, arg1: any, arg2: any, arg3: any) => void;
registeredComponents?: {
getRegisteredComponentsCount: () => number,
getId: (index) => number,
getAssembly: (id) => System_String,
getTypeName: (id) => System_String,
getParameterDefinitions: (id) => System_String,
getParameterValues: (id) => any,
getRegisteredComponentsCount: () => number;
getId: (index) => number;
getAssembly: (id) => string;
getTypeName: (id) => string;
getParameterDefinitions: (id) => string;
getParameterValues: (id) => any;
};
renderBatch?: (browserRendererId: number, batchAddress: Pointer) => void,
getConfig?: (dotNetFileName: System_String) => System_Object | undefined,
getApplicationEnvironment?: () => System_String,
dotNetCriticalError?: any
loadLazyAssembly?: any,
loadSatelliteAssemblies?: any,
sendJSDataStream?: (data: any, streamId: number, chunkSize: number) => void,
getJSDataStreamChunk?: (data: any, position: number, chunkSize: number) => Promise<Uint8Array>,
receiveDotNetDataStream?: (streamId: number, data: any, bytesRead: number, errorMessage: string) => void,
attachWebRendererInterop?: typeof attachWebRendererInterop,
renderBatch?: (browserRendererId: number, batchAddress: Pointer) => void;
getConfig?: (fileName: string) => Uint8Array | undefined;
getApplicationEnvironment?: () => string;
dotNetCriticalError?: any;
loadLazyAssembly?: any;
loadSatelliteAssemblies?: any;
sendJSDataStream?: (data: any, streamId: number, chunkSize: number) => void;
getJSDataStreamChunk?: (data: any, position: number, chunkSize: number) => Promise<Uint8Array>;
receiveDotNetDataStream?: (streamId: number, data: any, bytesRead: number, errorMessage: string) => void;
attachWebRendererInterop?: typeof attachWebRendererInterop;

// JSExport APIs
dotNetExports?: {
InvokeDotNet: (assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number, argsJson: string) => string | null;
EndInvokeJS: (argsJson: string) => void;
BeginInvokeDotNet: (callId: string | null, assemblyNameOrDotNetObjectId: string, methodIdentifier: string, argsJson: string) => void;
ReceiveByteArrayFromJS: (id: number, data: Uint8Array) => void;
}

// APIs invoked by hot reload
applyHotReload?: (id: string, metadataDelta: string, ilDelta: string, pdbDelta: string | undefined) => void,
getApplyUpdateCapabilities?: () => string,
applyHotReload?: (id: string, metadataDelta: string, ilDelta: string, pdbDelta: string | undefined) => void;
getApplyUpdateCapabilities?: () => string;
}
}

Expand Down
30 changes: 11 additions & 19 deletions src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ async function createRuntimeInstance(resourceLoader: WebAssemblyResourceLoader):
setModuleImports('blazor-internal', {
Blazor: { _internal: Blazor._internal },
});
const exports = await runtime.getAssemblyExports('Microsoft.AspNetCore.Components.WebAssembly');
Object.assign(Blazor._internal, {
dotNetExports: {
...exports.Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime,
},
});
attachInteropInvoker();
if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) {
resourceLoader.logToConsole();
Expand Down Expand Up @@ -412,7 +418,6 @@ async function loadSatelliteAssemblies(resourceLoader: WebAssemblyResourceLoader
}));
}


async function loadLazyAssembly(resourceLoader: WebAssemblyResourceLoader, assemblyNameToLoad: string): Promise<{ dll: Uint8Array, pdb: Uint8Array | null }> {
const resources = resourceLoader.bootConfig.resources;
const lazyAssemblies = resources.lazyAssembly;
Expand Down Expand Up @@ -449,19 +454,7 @@ function getArrayDataPointer<T>(array: System_Array<T>): number {
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
}

function bindStaticMethod(assembly: string, typeName: string, method: string) {
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
const fqn = `[${assembly}] ${typeName}:${method}`;
return BINDING.bind_static_method(fqn);
}

export let byteArrayBeingTransferred: Uint8Array | null = null;
function attachInteropInvoker(): void {
const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'InvokeDotNet');
const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'BeginInvokeDotNet');
const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'EndInvokeJS');
const dotNetDispatcherNotifyByteArrayAvailableMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'NotifyByteArrayAvailable');

DotNet.attachDispatcher({
beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => {
assertHeapIsNotLocked();
Expand All @@ -474,26 +467,25 @@ function attachInteropInvoker(): void {
? dotNetObjectId.toString()
: assemblyName;

dotNetDispatcherBeginInvokeMethodHandle(
Blazor._internal.dotNetExports!.BeginInvokeDotNet!(
callId ? callId.toString() : null,
assemblyNameOrDotNetObjectId,
methodIdentifier,
argsJson,
);
},
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
dotNetDispatcherEndInvokeJSMethodHandle(serializedArgs);
Blazor._internal.dotNetExports!.EndInvokeJS(serializedArgs);
},
sendByteArray: (id: number, data: Uint8Array): void => {
byteArrayBeingTransferred = data;
dotNetDispatcherNotifyByteArrayAvailableMethodHandle(id);
Blazor._internal.dotNetExports!.ReceiveByteArrayFromJS(id, data);
},
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
assertHeapIsNotLocked();
return dotNetDispatcherInvokeMethodHandle(
return Blazor._internal.dotNetExports!.InvokeDotNet(
assemblyName ? assemblyName : null,
methodIdentifier,
dotNetObjectId ? dotNetObjectId.toString() : null,
dotNetObjectId ?? 0,
argsJson,
) as string;
},
Expand Down
9 changes: 3 additions & 6 deletions src/Components/Web.JS/src/Platform/WebAssemblyConfigLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@

import { BootConfigResult } from './BootConfig';
import { WebAssemblyStartOptions } from './WebAssemblyStartOptions';
import { System_String, System_Object } from './Platform';
import { Blazor } from '../GlobalExports';
import { BINDING } from './Mono/MonoPlatform';

export class WebAssemblyConfigLoader {
static async initAsync(bootConfigResult: BootConfigResult, startOptions: Partial<WebAssemblyStartOptions>): Promise<void> {
Blazor._internal.getApplicationEnvironment = () => BINDING.js_string_to_mono_string(bootConfigResult.applicationEnvironment);
Blazor._internal.getApplicationEnvironment = () => bootConfigResult.applicationEnvironment;

const configFiles = await Promise.all((bootConfigResult.bootConfig.config || [])
.filter(name => name === 'appsettings.json' || name === `appsettings.${bootConfigResult.applicationEnvironment}.json`)
.map(async name => ({ name, content: await getConfigBytes(name) })));

Blazor._internal.getConfig = (dotNetFileName: System_String) : System_Object | undefined => {
const fileName = BINDING.conv_string(dotNetFileName);
Blazor._internal.getConfig = (fileName: string) : Uint8Array | undefined => {
const resolvedFile = configFiles.find(f => f.name === fileName);
return resolvedFile ? BINDING.js_typed_array_to_array(resolvedFile.content) : undefined;
return resolvedFile ? resolvedFile.content : undefined;
};

async function getConfigBytes(file: string): Promise<Uint8Array> {
Expand Down
8 changes: 0 additions & 8 deletions src/Components/Web/src/Forms/InputFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public class InputFile : ComponentBase, IInputFileJsCallbacks, IDisposable
{
private ElementReference _inputFileElement;

private IJSUnmarshalledRuntime? _jsUnmarshalledRuntime;

private InputFileJsCallbacksRelay? _jsCallbacksRelay;

[Inject]
Expand Down Expand Up @@ -46,12 +44,6 @@ public ElementReference? Element
protected set => _inputFileElement = value!.Value;
}

/// <inheritdoc/>
protected override void OnInitialized()
{
_jsUnmarshalledRuntime = JSRuntime as IJSUnmarshalledRuntime;
}

/// <inheritdoc/>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
Expand Down
1 change: 1 addition & 0 deletions src/Components/Web/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
*REMOVED*override Microsoft.AspNetCore.Components.Forms.InputFile.OnInitialized() -> void
Microsoft.AspNetCore.Components.Web.HtmlComponent
Microsoft.AspNetCore.Components.Web.HtmlComponent.ToHtmlString() -> string!
Microsoft.AspNetCore.Components.Web.HtmlComponent.WaitForQuiescenceAsync() -> System.Threading.Tasks.Task!
Expand Down
35 changes: 26 additions & 9 deletions src/Components/WebAssembly/JSInterop/src/InternalCalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,36 @@

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;

namespace WebAssembly.JSInterop;

/// <summary>
/// Methods that map to the functions compiled into the Mono WebAssembly runtime,
/// as defined by 'mono_add_internal_call' calls in driver.c.
/// </summary>
internal static class InternalCalls
internal static partial class InternalCalls
{
// The exact namespace, type, and method names must match the corresponding entries
// in driver.c in the Mono distribution
/// See: https://github.com/mono/mono/blob/90574987940959fe386008a850982ea18236a533/sdks/wasm/src/driver.c#L318-L319

// This method only exists for backwards compatibility and will be removed in the future.
// The exact namespace, type, and method name must match the corresponding entries
// in driver.c in the Mono distribution.
// See: https://github.com/mono/mono/blob/90574987940959fe386008a850982ea18236a533/sdks/wasm/src/driver.c#L318-L319
[MethodImpl(MethodImplOptions.InternalCall)]
[Obsolete]
public static extern TRes InvokeJS<T0, T1, T2, TRes>(out string exception, ref JSCallInfo callInfo, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2);

[JSImport("Blazor._internal.invokeJSJson", "blazor-internal")]
public static partial string InvokeJSJson(
string identifier,
[JSMarshalAs<JSType.Number>] long targetInstanceId,
int resultType,
string argsJson,
[JSMarshalAs<JSType.Number>] long asyncHandle);

[JSImport("Blazor._internal.endInvokeDotNetFromJS", "blazor-internal")]
public static partial void EndInvokeDotNetFromJS(
string? id,
bool success,
string jsonOrError);

[JSImport("Blazor._internal.receiveByteArray", "blazor-internal")]
public static partial void ReceiveByteArray(
int id,
byte[] data);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<IsShippingPackage>true</IsShippingPackage>
<Nullable>enable</Nullable>
<IsTrimmable>true</IsTrimmable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- TODO: Address Native AOT analyzer warnings https://github.com/dotnet/aspnetcore/issues/45473 -->
<EnableAOTAnalyzer>false</EnableAOTAnalyzer>
</PropertyGroup>
Expand Down
Loading

0 comments on commit 8fbf7f2

Please sign in to comment.