Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 8 additions & 50 deletions src/Components/Web.JS/src/Boot.WebAssembly.Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.

/* eslint-disable array-element-newline */
import { DotNet } from '@microsoft/dotnet-js-interop';
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Blazor } from './GlobalExports';
import * as Environment from './Environment';
import { BINDING, monoPlatform, dispatcher, getInitializer } from './Platform/Mono/MonoPlatform';
import { monoPlatform, dispatcher, getInitializer } from './Platform/Mono/MonoPlatform';
import { renderBatch, getRendererer, attachRootComponentToElement, attachRootComponentToLogicalElement } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { PlatformApi, Pointer } from './Platform/Platform';
import { Pointer } from './Platform/Platform';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
import { WebAssemblyComponentDescriptor, discoverWebAssemblyPersistedState } from './Services/ComponentDescriptorDiscovery';
Expand All @@ -19,7 +19,6 @@ import { RootComponentManager } from './Services/RootComponentManager';
import { WebRendererId } from './Rendering/WebRendererId';

let options: Partial<WebAssemblyStartOptions> | undefined;
let initializersPromise: Promise<void>;
let platformLoadPromise: Promise<void> | undefined;
let loadedWebAssemblyPlatform = false;
let started = false;
Expand All @@ -43,7 +42,7 @@ export function resolveInitialUpdate(value: string): void {
}

let resolveInitializersPromise: (value: void) => void;
initializersPromise = new Promise<void>(resolve => {
const initializersPromise = new Promise<void>(resolve => {
resolveInitializersPromise = resolve;
});

Expand Down Expand Up @@ -105,7 +104,6 @@ async function startCore(components: RootComponentManager<WebAssemblyComponentDe
Blazor._internal.getApplyUpdateCapabilities = () => dispatcher.invokeDotNetStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'GetApplyUpdateCapabilities');

// Configure JS interop
Blazor._internal.invokeJSFromDotNet = invokeJSFromDotNet;
Blazor._internal.invokeJSJson = invokeJSJson;
Blazor._internal.endInvokeDotNetFromJS = endInvokeDotNetFromJS;
Blazor._internal.receiveWebAssemblyDotNetDataStream = receiveWebAssemblyDotNetDataStream;
Expand Down Expand Up @@ -169,7 +167,7 @@ async function startCore(components: RootComponentManager<WebAssemblyComponentDe
Blazor._internal.endUpdateRootComponents = (batchId: number) =>
components.onAfterUpdateRootComponents?.(batchId);

Blazor._internal.attachRootComponentToElement = (selector, componentId, rendererId: any) => {
Blazor._internal.attachRootComponentToElement = (selector, componentId, rendererId) => {
const element = componentAttacher.resolveRegisteredElement(selector);
if (!element) {
attachRootComponentToElement(selector, componentId, rendererId);
Expand All @@ -178,17 +176,16 @@ async function startCore(components: RootComponentManager<WebAssemblyComponentDe
}
};

let api: PlatformApi;
try {
await platformLoadPromise;
api = await platform.start();
await platform.start();
} catch (ex) {
throw new Error(`Failed to start platform. Reason: ${ex}`);
}

// Start up the application
platform.callEntryPoint();
// At this point .NET has been initialized (and has yielded), we can't await the promise becasue it will
// At this point .NET has been initialized (and has yielded), we can't await the promise because it will
// only end when the app finishes running
const initializer = getInitializer();
initializer.invokeAfterStartedCallbacks(Blazor);
Expand Down Expand Up @@ -229,45 +226,6 @@ export function hasLoadedWebAssemblyPlatform(): boolean {
return loadedWebAssemblyPlatform;
}

// obsolete, legacy, don't use for new code!
function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
const functionIdentifier = monoPlatform.readStringField(callInfo, 0)!;
const resultType = monoPlatform.readInt32Field(callInfo, 4);
const marshalledCallArgsJson = monoPlatform.readStringField(callInfo, 8);
const targetInstanceId = monoPlatform.readUint64Field(callInfo, 20);

if (marshalledCallArgsJson !== null) {
const marshalledCallAsyncHandle = monoPlatform.readUint64Field(callInfo, 12);

if (marshalledCallAsyncHandle !== 0) {
dispatcher.beginInvokeJSFromDotNet(marshalledCallAsyncHandle, functionIdentifier, marshalledCallArgsJson, resultType, targetInstanceId);
return 0;
} else {
const resultJson = dispatcher.invokeJSFromDotNet(functionIdentifier, marshalledCallArgsJson, resultType, targetInstanceId)!;
return resultJson === null ? 0 : BINDING.js_string_to_mono_string(resultJson);
}
} else {
const func = DotNet.findJSFunction(functionIdentifier, targetInstanceId);
const result = func.call(null, arg0, arg1, arg2);

switch (resultType) {
case DotNet.JSCallResultType.Default:
return result;
case DotNet.JSCallResultType.JSObjectReference:
return DotNet.createJSObjectReference(result).__jsObjectId;
case DotNet.JSCallResultType.JSStreamReference: {
const streamReference = DotNet.createJSStreamReference(result);
const resultJson = JSON.stringify(streamReference);
return BINDING.js_string_to_mono_string(resultJson);
}
case DotNet.JSCallResultType.JSVoidResult:
return null;
default:
throw new Error(`Invalid JS call result type '${resultType}'.`);
}
}
}

export function updateWebAssemblyRootComponents(operations: string): void {
if (!startPromise) {
throw new Error('Blazor WebAssembly has not started.');
Expand Down Expand Up @@ -307,7 +265,7 @@ function endInvokeDotNetFromJS(callId: string, success: boolean, resultJsonOrErr
dispatcher.endInvokeDotNetFromJS(callId, success, resultJsonOrErrorMessage);
}

function receiveWebAssemblyDotNetDataStream(streamId: number, data: any, bytesRead: number, errorMessage: string): void {
function receiveWebAssemblyDotNetDataStream(streamId: number, data: Uint8Array, bytesRead: number, errorMessage: string): void {
receiveDotNetDataStream(dispatcher, streamId, data, bytesRead, errorMessage);
}

Expand Down
12 changes: 2 additions & 10 deletions src/Components/Web.JS/src/Boot.WebAssembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

/* eslint-disable array-element-newline */
import { Blazor } from './GlobalExports';
import { Module } from './Platform/Mono/MonoPlatform';
import { shouldAutoStart } from './BootCommon';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { setWebAssemblyOptions, startWebAssembly } from './Boot.WebAssembly.Common';
import { WebAssemblyComponentDescriptor, discoverComponents } from './Services/ComponentDescriptorDiscovery';
import { DotNet } from '@microsoft/dotnet-js-interop';
import { InitialRootComponentsList } from './Services/InitialRootComponentsList';
import { JSEventRegistry } from './Services/JSEventRegistry';
import { printErr } from './Platform/Mono/MonoPlatform';

let started = false;

Expand All @@ -32,13 +32,5 @@ Blazor.start = boot;
window['DotNet'] = DotNet;

if (shouldAutoStart()) {
boot().catch(error => {
if (typeof Module !== 'undefined' && Module.err) {
// Logs it, and causes the error UI to appear
Module.err(error);
} else {
// The error must have happened so early we didn't yet set up the error UI, so just log to console
console.error(error);
}
});
boot().catch(printErr);
}
1 change: 0 additions & 1 deletion src/Components/Web.JS/src/GlobalExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export interface IBlazor {
forceCloseConnection?: () => Promise<void>;
InputFile?: typeof InputFile;
NavigationLock: typeof NavigationLock;
invokeJSFromDotNet?: (callInfo: Pointer, arg0: any, arg1: any, arg2: any) => any;
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;
Expand Down
83 changes: 15 additions & 68 deletions src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,22 @@ import { showErrorNotification } from '../../BootErrors';
import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock, PlatformApi } from '../Platform';
import { WebAssemblyBootResourceType, WebAssemblyStartOptions } from '../WebAssemblyStartOptions';
import { Blazor } from '../../GlobalExports';
import { DotnetModuleConfig, EmscriptenModule, MonoConfig, ModuleAPI, RuntimeAPI, GlobalizationMode } from 'dotnet-runtime';
import { BINDINGType, MONOType } from 'dotnet-runtime/dotnet-legacy';
import { DotnetModuleConfig, MonoConfig, ModuleAPI, RuntimeAPI, GlobalizationMode } from 'dotnet-runtime';
import { fetchAndInvokeInitializers } from '../../JSInitializers/JSInitializers.WebAssembly';
import { JSInitializer } from '../../JSInitializers/JSInitializers';
import { WebRendererId } from '../../Rendering/WebRendererId';

// initially undefined and only fully initialized after createEmscriptenModuleInstance()
export let BINDING: BINDINGType = undefined as any;
export let MONO: MONOType = undefined as any;
export let Module: DotnetModuleConfig & EmscriptenModule = undefined as any;
export let dispatcher: DotNet.ICallDispatcher = undefined as any;
let MONO_INTERNAL: any = undefined as any;
let runtime: RuntimeAPI = undefined as any;
let jsInitializer: JSInitializer;

const uint64HighOrderShift = Math.pow(2, 32);
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER

let currentHeapLock: MonoHeapLock | null = null;

// Memory access helpers
// The implementations are exactly equivalent to what the global getValue(addr, type) function does,
// except without having to parse the 'type' parameter, and with less risk of mistakes at the call site
function getValueI16(ptr: number) {
return MONO.getI16(ptr);
}
function getValueI32(ptr: number) {
return MONO.getI32(ptr);
}
function getValueFloat(ptr: number) {
return MONO.getF32(ptr);
}

export function getInitializer() {
return jsInitializer;
}

function getValueU64(ptr: number) {
// There is no Module.HEAPU64, and Module.getValue(..., 'i64') doesn't work because the implementation
// treats 'i64' as being the same as 'i32'. Also we must take care to read both halves as unsigned.
const heapU32Index = ptr >> 2;
const highPart = Module.HEAPU32[heapU32Index + 1];
if (highPart > maxSafeNumberHighPart) {
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
}

return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index];
}

export const monoPlatform: Platform = {
load: function load(options: Partial<WebAssemblyStartOptions>, onConfigLoaded?: (loadedConfig: MonoConfig) => void) {
return createRuntimeInstance(options, onConfigLoaded);
Expand All @@ -77,18 +44,6 @@ export const monoPlatform: Platform = {
}
},

toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array {
const dataPtr = getArrayDataPointer(array);
const length = getValueI32(dataPtr);
const uint8Array = new Uint8Array(length);
uint8Array.set(Module.HEAPU8.subarray(dataPtr + 4, dataPtr + 4 + length));
return uint8Array;
},

getArrayLength: function getArrayLength(array: System_Array<any>): number {
return getValueI32(getArrayDataPointer(array));
},

getArrayEntryPtr: function getArrayEntryPtr<TPtr extends Pointer>(array: System_Array<TPtr>, index: number, itemSize: number): TPtr {
// First byte is array length, followed by entries
const address = getArrayDataPointer(array) + 4 + index * itemSize;
Expand All @@ -97,46 +52,42 @@ export const monoPlatform: Platform = {

getObjectFieldsBaseAddress: function getObjectFieldsBaseAddress(referenceTypedObject: System_Object): Pointer {
// The first two int32 values are internal Mono data
return (referenceTypedObject as any as number + 8) as any as Pointer;
return (referenceTypedObject as any + 8) as any as Pointer;
},

readInt16Field: function readHeapInt16(baseAddress: Pointer, fieldOffset?: number): number {
return getValueI16((baseAddress as any as number) + (fieldOffset || 0));
return runtime.getHeapI16((baseAddress as any) + (fieldOffset || 0));
},

readInt32Field: function readHeapInt32(baseAddress: Pointer, fieldOffset?: number): number {
return getValueI32((baseAddress as unknown as number) + (fieldOffset || 0));
return runtime.getHeapI32((baseAddress as any) + (fieldOffset || 0));
},

readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number {
return getValueU64((baseAddress as unknown as number) + (fieldOffset || 0));
},

readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number {
return getValueFloat((baseAddress as unknown as number) + (fieldOffset || 0));
return runtime.getHeapU52((baseAddress as any) + (fieldOffset || 0));
},

readObjectField: function readHeapObject<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T {
return getValueI32((baseAddress as unknown as number) + (fieldOffset || 0)) as any as T;
readObjectField: function readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T {
return runtime.getHeapU32((baseAddress as any) + (fieldOffset || 0)) as any as T;
},

readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null {
const fieldValue = getValueI32((baseAddress as unknown as number) + (fieldOffset || 0));
readStringField: function readStringField(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null {
const fieldValue = runtime.getHeapU32((baseAddress as any) + (fieldOffset || 0));
if (fieldValue === 0) {
return null;
}

if (readBoolValueAsString) {
// Some fields are stored as a union of bool | string | null values, but need to read as a string.
// If the stored value is a bool, the behavior we want is empty string ('') for true, or null for false.
const unboxedValue = BINDING.unbox_mono_obj(fieldValue as any as System_Object);

const unboxedValue = MONO_INTERNAL.monoObjectAsBoolOrNullUnsafe(fieldValue as any as System_Object);
if (typeof (unboxedValue) === 'boolean') {
return unboxedValue ? '' : null;
}
return unboxedValue;
}

return BINDING.conv_string(fieldValue as any as System_String);
return MONO_INTERNAL.monoStringToStringUnsafe(fieldValue as any as System_String);
},

readStructField: function readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T {
Expand Down Expand Up @@ -206,13 +157,12 @@ function prepareRuntimeConfig(options: Partial<WebAssemblyStartOptions>, onConfi
jsInitializer = await fetchAndInvokeInitializers(options, loadedConfig);
};

const moduleConfig = (window['Module'] || {}) as typeof Module;
const moduleConfig = (window['Module'] || {}) as any;
const dotnetModuleConfig: DotnetModuleConfig = {
...moduleConfig,
onConfigLoaded: (onConfigLoaded as (config: MonoConfig) => void | Promise<void>),
onDownloadResourceProgress: setProgress,
config,
disableDotnet6Compatibility: false,
out: print,
err: printErr,
};
Expand Down Expand Up @@ -251,10 +201,7 @@ async function configureRuntimeInstance(): Promise<PlatformApi> {
throw new Error('The runtime must be loaded it gets configured.');
}

const { MONO: mono, BINDING: binding, Module: module, setModuleImports, INTERNAL: mono_internal, getConfig, invokeLibraryInitializers } = runtime;
Module = module;
BINDING = binding;
MONO = mono;
const { setModuleImports, INTERNAL: mono_internal, getConfig, invokeLibraryInitializers } = runtime;
MONO_INTERNAL = mono_internal;

attachDebuggerHotkey(getConfig());
Expand Down Expand Up @@ -285,7 +232,7 @@ function setProgress(resourcesLoaded, totalResources) {

const suppressMessages = ['DEBUGGING ENABLED'];
const print = line => (suppressMessages.indexOf(line) < 0 && console.log(line));
const printErr = line => {
export const printErr = line => {
// 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.
Expand Down
34 changes: 0 additions & 34 deletions src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts

This file was deleted.

6 changes: 2 additions & 4 deletions src/Components/Web.JS/src/Platform/Platform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { MonoObject, MonoString, MonoArray } from 'dotnet-runtime/dotnet-legacy';
import { WebAssemblyStartOptions } from './WebAssemblyStartOptions';
import { MonoConfig } from 'dotnet-runtime';
Expand All @@ -11,16 +13,12 @@ export interface Platform {

callEntryPoint(): Promise<unknown>;

toUint8Array(array: System_Array<unknown>): Uint8Array;

getArrayLength(array: System_Array<unknown>): number;
getArrayEntryPtr<TPtr extends Pointer>(array: System_Array<TPtr>, index: number, itemSize: number): TPtr;

getObjectFieldsBaseAddress(referenceTypedObject: System_Object): Pointer;
readInt16Field(baseAddress: Pointer, fieldOffset?: number): number;
readInt32Field(baseAddress: Pointer, fieldOffset?: number): number;
readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T;
readStringField(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null;
readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T;
Expand Down
Loading