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
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webview.js

Large diffs are not rendered by default.

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';
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 @@ -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) || '',
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 +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,
},
});
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved
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) {
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 +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
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 +1,2 @@
#nullable enable
*REMOVED*override Microsoft.AspNetCore.Components.Forms.InputFile.OnInitialized() -> void
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]
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