Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate framework use of 'IJSUnmarshalledRuntime' #46693

Merged
merged 12 commits into from
Mar 7, 2023
Merged
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
54 changes: 24 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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In next PR we could try re-invent how the goal of invokeJSFromDotNet could be achieved otherwise.
To ged rid of things like BINDING.js_string_to_mono_string

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 @@ -44,6 +44,11 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
}
});

Blazor._internal.initHotReload = async (url: string) => {
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
const module = await import(/* webpackIgnore: true */ url);
return module['receiveHotReload']();
};

Blazor._internal.applyHotReload = (id: string, metadataDelta: string, ilDelta: string, pdbDelta: string | undefined) => {
DotNet.invokeMethod('Microsoft.AspNetCore.Components.WebAssembly', 'ApplyHotReloadDelta', id, metadataDelta, ilDelta, pdbDelta);
};
Expand All @@ -52,9 +57,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 +78,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 +113,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) || '',
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
};

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 +190,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
65 changes: 36 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, System_String, System_Array, System_Boolean, System_Byte, System_Int } from './Platform/Platform';
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
import { getNextChunk, receiveDotNetDataStream } from './StreamingInterop';
import { RootComponentsFunctions } from './Rendering/JSRootComponents';
import { attachWebRendererInterop } from './Rendering/WebRendererInteropMethods';
Expand All @@ -29,41 +29,48 @@ 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
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,
initHotReload?: (url: string) => Promise<void>;
applyHotReload?: (id: string, metadataDelta: string, ilDelta: string, pdbDelta: string | undefined) => void;
getApplyUpdateCapabilities?: () => string;
}
}

Expand Down
27 changes: 9 additions & 18 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,10 @@ 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, {
...exports.Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime,
});
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
attachInteropInvoker();
if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) {
resourceLoader.logToConsole();
Expand Down Expand Up @@ -449,19 +453,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) {
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
// 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 +466,25 @@ function attachInteropInvoker(): void {
? dotNetObjectId.toString()
: assemblyName;

dotNetDispatcherBeginInvokeMethodHandle(
Blazor._internal.BeginInvokeDotNet!(
callId ? callId.toString() : null,
assemblyNameOrDotNetObjectId,
methodIdentifier,
argsJson,
);
},
endInvokeJSFromDotNet: (asyncHandle, succeeded, serializedArgs): void => {
dotNetDispatcherEndInvokeJSMethodHandle(serializedArgs);
Blazor._internal.EndInvokeJS!(serializedArgs);
},
sendByteArray: (id: number, data: Uint8Array): void => {
byteArrayBeingTransferred = data;
dotNetDispatcherNotifyByteArrayAvailableMethodHandle(id);
Blazor._internal.ReceiveByteArrayFromJS!(id, data);
},
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
assertHeapIsNotLocked();
return dotNetDispatcherInvokeMethodHandle(
return Blazor._internal.InvokeDotNet!(
assemblyName ? assemblyName : null,
methodIdentifier,
dotNetObjectId ? dotNetObjectId.toString() : null,
dotNetObjectId ?? 0,
argsJson,
) as string;
},
Expand Down
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
3 changes: 0 additions & 3 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 @@ -49,7 +47,6 @@ public ElementReference? Element
/// <inheritdoc/>
protected override void OnInitialized()
{
_jsUnmarshalledRuntime = JSRuntime as IJSUnmarshalledRuntime;
}
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc/>
Expand Down
23 changes: 22 additions & 1 deletion src/Components/WebAssembly/JSInterop/src/InternalCalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,40 @@

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

[MethodImpl(MethodImplOptions.InternalCall)]
[Obsolete]
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
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