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
2 changes: 2 additions & 0 deletions src/native/libs/Common/JavaScript/cross-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export function dotnetUpdateInternalsSubscriber() {
abortStartup: table[15],
quitNow: table[16],
normalizeException: table[17],
fetchSatelliteAssemblies: table[18],
fetchLazyAssembly: table[19],
};
Object.assign(dotnetLoaderExports, loaderExportsLocal);
Object.assign(logger, loggerLocal);
Expand Down
26 changes: 22 additions & 4 deletions src/native/libs/Common/JavaScript/host/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,22 @@ import { browserVirtualAppBase, sizeOfPtr } from "../per-module";
const hasInstantiateStreaming = typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiateStreaming === "function";
const loadedAssemblies: Map<string, { ptr: number, length: number }> = new Map();

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) {
// WASM-TODO: https://github.com/dotnet/runtime/issues/122921
const lastSlash = virtualPath.lastIndexOf("/");
let parentDirectory = lastSlash > 0
? virtualPath.substring(0, lastSlash)
: browserVirtualAppBase;
let fileName = lastSlash > 0 ? virtualPath.substring(lastSlash + 1) : virtualPath;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
if (!parentDirectory.startsWith("/")) {
parentDirectory = browserVirtualAppBase + parentDirectory;
}

_ems_.dotnetLogger.debug(`Registering PDB '${fileName}' in directory '${parentDirectory}'`);
_ems_.FS.createPath("/", parentDirectory, true, true);
_ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */);
}

export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
Expand All @@ -25,7 +38,10 @@ export function registerDllBytes(bytes: Uint8Array, virtualPath: string) {
const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2];
_ems_.HEAPU8.set(bytes, ptr >>> 0);

const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
const name = virtualPath.startsWith(browserVirtualAppBase)
? virtualPath.substring(browserVirtualAppBase.length)
: virtualPath.substring(virtualPath.lastIndexOf("/") + 1);

_ems_.dotnetLogger.debug(`Registered assembly '${virtualPath}' (name: '${name}') at ${ptr.toString(16)} length ${bytes.length}`);
loadedAssemblies.set(virtualPath, { ptr, length: bytes.length });
loadedAssemblies.set(name, { ptr, length: bytes.length });
Expand Down Expand Up @@ -69,7 +85,9 @@ export async function instantiateWebcilModule(webcilPromise: Promise<Response>,
const getWebcilPayload = instance.exports.getWebcilPayload as (ptr: number, size: number) => void;
getWebcilPayload(payloadPtr, payloadSize);

const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
const name = virtualPath.startsWith(browserVirtualAppBase)
? virtualPath.substring(browserVirtualAppBase.length)
: virtualPath.substring(virtualPath.lastIndexOf("/") + 1);
_ems_.dotnetLogger.debug(`Registered Webcil assembly '${virtualPath}' (name: '${name}') at ${payloadPtr.toString(16)} length ${payloadSize}`);
loadedAssemblies.set(virtualPath, { ptr: payloadPtr, length: payloadSize });
loadedAssemblies.set(name, { ptr: payloadPtr, length: payloadSize });
Expand Down
95 changes: 91 additions & 4 deletions src/native/libs/Common/JavaScript/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let currentParallelDownloads = 0;
let downloadedAssetsCount = 0;
let totalAssetsToDownload = 0;
let loadBootResourceCallback: LoadBootResourceCallback | undefined = undefined;
const loadedLazyAssemblies = new Set<string>();

export function setLoadBootResourceCallback(callback: LoadBootResourceCallback | undefined): void {
loadBootResourceCallback = callback;
Expand Down Expand Up @@ -92,12 +93,17 @@ export async function fetchDll(asset: AssemblyAsset): Promise<void> {
totalAssetsToDownload++;
const assetInternal = asset as AssetEntryInternal;
dotnetAssert.check(assetInternal.virtualPath, "Assembly asset must have virtualPath");
if (assetInternal.name && !asset.resolvedUrl) {
asset.resolvedUrl = locateFile(assetInternal.name);
const assetNameForUrl = assetInternal.culture
? `${assetInternal.culture}/${assetInternal.name}`
: assetInternal.name;
if (assetNameForUrl && !asset.resolvedUrl) {
asset.resolvedUrl = locateFile(assetNameForUrl);
}
assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/")
? assetInternal.virtualPath
: browserVirtualAppBase + assetInternal.virtualPath;
: assetInternal.culture
? `${browserVirtualAppBase}${assetInternal.culture}/${assetInternal.virtualPath}`
: browserVirtualAppBase + assetInternal.virtualPath;
if (assetInternal.virtualPath.endsWith(".wasm")) {
assetInternal.behavior = "webcil10";
const webcilPromise = loadResource(assetInternal);
Expand All @@ -110,7 +116,6 @@ export async function fetchDll(asset: AssemblyAsset): Promise<void> {
assetInternal.behavior = "assembly";
const bytes = await fetchBytes(assetInternal);
onDownloadedAsset();

await nativeModulePromiseController.promise;
if (bytes) {
dotnetBrowserHostExports.registerDllBytes(bytes, assetInternal.virtualPath);
Expand Down Expand Up @@ -154,6 +159,88 @@ export async function fetchVfs(asset: AssemblyAsset): Promise<void> {
}
}

export async function fetchSatelliteAssemblies(culturesToLoad: string[]): Promise<void> {
const satelliteResources = loaderConfig.resources?.satelliteResources;
if (!satelliteResources) {
return;
}

const promises: Promise<void>[] = [];
for (const culture of culturesToLoad) {
if (!Object.prototype.hasOwnProperty.call(satelliteResources, culture)) {
continue;
}
for (const asset of satelliteResources[culture]) {
const assetInternal = asset as AssetEntryInternal;
assetInternal.culture = culture;
promises.push(fetchDll(asset));
}
}
await Promise.all(promises);
}

export async function fetchLazyAssembly(assemblyNameToLoad: string): Promise<boolean> {
const lazyAssemblies = loaderConfig.resources?.lazyAssembly;
if (!lazyAssemblies) {
throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly.");
}

let assemblyNameWithoutExtension = assemblyNameToLoad;
if (assemblyNameToLoad.endsWith(".dll"))
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 4);
else if (assemblyNameToLoad.endsWith(".wasm"))
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 5);

const assemblyNameToLoadDll = assemblyNameWithoutExtension + ".dll";
const assemblyNameToLoadWasm = assemblyNameWithoutExtension + ".wasm";

let dllAsset: AssemblyAsset | null = null;
for (const asset of lazyAssemblies) {
if (asset.virtualPath === assemblyNameToLoadDll || asset.virtualPath === assemblyNameToLoadWasm) {
dllAsset = asset;
break;
}
}

if (!dllAsset) {
throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
}

if (loadedLazyAssemblies.has(dllAsset.virtualPath)) {
return false;
}

await fetchDll(dllAsset);
loadedLazyAssemblies.add(dllAsset.virtualPath);

if (loaderConfig.debugLevel !== 0) {
const pdbNameToLoad = assemblyNameWithoutExtension + ".pdb";
const pdbAssets = loaderConfig.resources?.pdb;
let pdbAssetToLoad: AssemblyAsset | undefined;
if (pdbAssets) {
for (const pdbAsset of pdbAssets) {
if (pdbAsset.virtualPath === pdbNameToLoad) {
pdbAssetToLoad = pdbAsset;
break;
}
}
}
if (!pdbAssetToLoad) {
for (const lazyAsset of lazyAssemblies) {
if (lazyAsset.virtualPath === pdbNameToLoad) {
pdbAssetToLoad = lazyAsset as AssemblyAsset;
break;
}
}
}
if (pdbAssetToLoad) {
await fetchPdb(pdbAssetToLoad);
}
}

return true;
}

export async function fetchNativeSymbols(asset: SymbolsAsset): Promise<void> {
totalAssetsToDownload++;
const assetInternal = asset as AssetEntryInternal;
Expand Down
5 changes: 5 additions & 0 deletions src/native/libs/Common/JavaScript/loader/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ function mergeResources(target: Assets, source: Assets): Assets {
for (const key in source.satelliteResources) {
source.satelliteResources![key] = [...target.satelliteResources![key] || [], ...source.satelliteResources![key] || []];
}
for (const key in target.satelliteResources) {
if (!Object.prototype.hasOwnProperty.call(source.satelliteResources, key)) {
source.satelliteResources![key] = target.satelliteResources![key] || [];
}
}
return Object.assign(target, source);
}

Expand Down
6 changes: 5 additions & 1 deletion src/native/libs/Common/JavaScript/loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { check, error, info, warn, debug, fastCheck, normalizeException } from "
import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise, abortStartup } from "./run";
import { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "./promise-completion-source";
import { instantiateMainWasm } from "./assets";
import { fetchLazyAssembly, fetchSatelliteAssemblies, instantiateMainWasm } from "./assets";

export function dotnetInitializeModule(): RuntimeAPI {

Expand Down Expand Up @@ -67,6 +67,8 @@ export function dotnetInitializeModule(): RuntimeAPI {
abortStartup,
quitNow,
normalizeException,
fetchSatelliteAssemblies,
fetchLazyAssembly,
};
Object.assign(dotnetLoaderExports, loaderFunctions);
const logger: LoggerType = {
Expand Down Expand Up @@ -114,6 +116,8 @@ export function dotnetInitializeModule(): RuntimeAPI {
dotnetLoaderExports.abortStartup,
dotnetLoaderExports.quitNow,
dotnetLoaderExports.normalizeException,
dotnetLoaderExports.fetchSatelliteAssemblies,
dotnetLoaderExports.fetchLazyAssembly,
];
}

Expand Down
8 changes: 5 additions & 3 deletions src/native/libs/Common/JavaScript/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import { exit, runtimeState } from "./exit";
import { createPromiseCompletionSource } from "./promise-completion-source";
import { getIcuResourceName } from "./icu";
import { loaderConfig, validateLoaderConfig } from "./config";
import { fetchDll, fetchIcu, fetchNativeSymbols, fetchPdb, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets";
import { fetchDll, fetchIcu, fetchNativeSymbols, fetchPdb, fetchSatelliteAssemblies, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets";
import { initPolyfills } from "./polyfills";
import { validateWasmFeatures } from "./bootstrap";

const runMainPromiseController = createPromiseCompletionSource<number>();

// WASM-TODO: downloadOnly - blazor render mode auto pre-download. Really no start.
// WASM-TODO: loadAllSatelliteResources
// WASM-TODO: debugLevel
// WASM-TODO: load symbolication json https://github.com/dotnet/runtime/issues/122647

// many things happen in parallel here, but order matters for performance!
// ideally we want to utilize network and CPU at the same time
Expand Down Expand Up @@ -56,6 +54,9 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
const coreVfsPromise = Promise.all((loaderConfig.resources.coreVfs || []).map(fetchVfs));

const assembliesPromise = Promise.all(loaderConfig.resources.assembly.map(fetchDll));
const satelliteResourcesPromise = loaderConfig.loadAllSatelliteResources && loaderConfig.resources.satelliteResources
? fetchSatelliteAssemblies(Object.keys(loaderConfig.resources.satelliteResources))
: Promise.resolve();
const vfsPromise = Promise.all((loaderConfig.resources.vfs || []).map(fetchVfs));

const icuResourceName = getIcuResourceName();
Expand Down Expand Up @@ -87,6 +88,7 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
}

await assembliesPromise;
await satelliteResourcesPromise;
await corePDBsPromise;
await pdbsPromise;
await runtimeModuleReady;
Expand Down
7 changes: 6 additions & 1 deletion src/native/libs/Common/JavaScript/types/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../
import type { initializeCoreCLR } from "../host/host";
import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes, instantiateWebcilModule } from "../host/assets";
import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../loader/promise-completion-source";
import type { fetchSatelliteAssemblies, fetchLazyAssembly } from "../loader/assets";

import type { isSharedArrayBuffer, zeroRegion } from "../../../System.Native.Browser/utils/memory";
import type { stringToUTF16, stringToUTF16Ptr, stringToUTF8, stringToUTF8Ptr, utf16ToString } from "../../../System.Native.Browser/utils/strings";
Expand Down Expand Up @@ -71,7 +72,9 @@ export type LoaderExports = {
addOnExitListener: typeof addOnExitListener,
abortStartup: typeof abortStartup,
quitNow: typeof quitNow,
normalizeException: typeof normalizeException
normalizeException: typeof normalizeException,
fetchSatelliteAssemblies: typeof fetchSatelliteAssemblies,
fetchLazyAssembly: typeof fetchLazyAssembly,
}

export type LoaderExportsTable = [
Expand All @@ -93,6 +96,8 @@ export type LoaderExportsTable = [
typeof abortStartup,
typeof quitNow,
typeof normalizeException,
typeof fetchSatelliteAssemblies,
typeof fetchLazyAssembly,
]

export type BrowserHostExports = {
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/Common/JavaScript/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface AssetEntryInternal extends AssetEntry {
integrity?: string
cache?: RequestCache
useCredentials?: boolean
culture?: string
}

export type LoaderConfigInternal = LoaderConfig & {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { dotnetLoaderExports } from "./cross-module";

export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise<void> {
throw new Error("TODO: loadSatelliteAssemblies is not implemented yet");
await dotnetLoaderExports.fetchSatelliteAssemblies(culturesToLoad);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function loadLazyAssembly(assemblyNameToLoad: string): Promise<boolean> {
throw new Error("TODO: loadLazyAssembly is not implemented yet");
return dotnetLoaderExports.fetchLazyAssembly(assemblyNameToLoad);
}
Loading