diff options
84 files changed, 9057 insertions, 1426 deletions
diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 6704af1c528..0c2e31734b9 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -210,7 +210,6 @@ <PlatformManifestFileEntry Include="libmono-profiler-aot.a" IsNative="true" /> <PlatformManifestFileEntry Include="libmono-wasm-eh-js.a" IsNative="true" /> <PlatformManifestFileEntry Include="libmono-wasm-eh-wasm.a" IsNative="true" /> - <PlatformManifestFileEntry Include="System.Private.Runtime.InteropServices.Javascript.dll" /> <PlatformManifestFileEntry Include="dotnet-crypto-worker.js" IsNative="true" /> <PlatformManifestFileEntry Include="dotnet.js" IsNative="true" /> <PlatformManifestFileEntry Include="dotnet.worker.js" IsNative="true" /> diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index cd2ef6f3cc3..d67afaba600 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -9,6 +9,23 @@ internal static partial class Interop internal static unsafe partial class Runtime { [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern unsafe void BindJSFunction(in string function_name, in string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void MarshalPromise(void* data); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void DeregisterGCRoot(IntPtr handle); + + #region Legacy + + [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern string InvokeJS(string str, out int exceptionalResult); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void InvokeJSWithArgsRef(IntPtr jsHandle, in string method, in object?[] parms, out int exceptionalResult, out object result); @@ -26,8 +43,6 @@ internal static partial class Interop [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void TypedArrayToArrayRef(IntPtr jsHandle, out int exceptionalResult, out object result); [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); - [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void CreateCSOwnedObjectRef(in string className, in object[] parms, out int exceptionalResult, out object result); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void TypedArrayCopyToRef(IntPtr jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult, out object result); @@ -36,21 +51,6 @@ internal static partial class Interop [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void TypedArrayCopyFromRef(IntPtr jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult, out object result); - - #region Legacy - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void WebSocketSend(IntPtr webSocketJSHandle, IntPtr messagePtr, int offset, int length, int messageType, bool endOfMessage, out IntPtr promiseJSHandle, out int exceptionalResult, out object result); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void WebSocketReceive(IntPtr webSocketJSHandle, IntPtr bufferPtr, int offset, int length, IntPtr responsePtr, out IntPtr promiseJSHandle, out int exceptionalResult, out object result); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void WebSocketOpenRef(in string uri, in object[]? subProtocols, in Delegate onClosed, out IntPtr webSocketJSHandle, out IntPtr promiseJSHandle, out int exceptionalResult, out object result); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void WebSocketAbort(IntPtr webSocketJSHandle, out int exceptionalResult, out string result); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void WebSocketCloseRef(IntPtr webSocketJSHandle, int code, in string? reason, bool waitForCloseReceived, out IntPtr promiseJSHandle, out int exceptionalResult, out object result); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void CancelPromiseRef(IntPtr promiseJSHandle, out int exceptionalResult, out string result); - #endregion } diff --git a/src/libraries/NetCoreAppLibrary.props b/src/libraries/NetCoreAppLibrary.props index 5cb1d73dc7d..25a0a917299 100644 --- a/src/libraries/NetCoreAppLibrary.props +++ b/src/libraries/NetCoreAppLibrary.props @@ -105,7 +105,6 @@ System.ObjectModel; System.Private.CoreLib; System.Private.DataContractSerialization; - System.Private.Runtime.InteropServices.JavaScript; System.Private.Uri; System.Private.Xml; System.Private.Xml.Linq; @@ -179,7 +178,6 @@ System.Net.Quic; System.Private.CoreLib; System.Private.DataContractSerialization; - System.Private.Runtime.InteropServices.JavaScript; System.Private.Uri; System.Private.Xml; System.Private.Xml.Linq; diff --git a/src/libraries/System.Console/src/System.Console.csproj b/src/libraries/System.Console/src/System.Console.csproj index 9b77767dcb6..b63628073c5 100644 --- a/src/libraries/System.Console/src/System.Console.csproj +++ b/src/libraries/System.Console/src/System.Console.csproj @@ -232,6 +232,7 @@ <Reference Include="Microsoft.Win32.Primitives" /> </ItemGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> - <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <Reference Include="System.Runtime.InteropServices.JavaScript" /> </ItemGroup> </Project> diff --git a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs index e72fe6c24a9..0594f0f3930 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs @@ -3,12 +3,9 @@ using System.IO; using System.Text; -using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using System.Runtime.InteropServices.JavaScript; -using JSObject = System.Runtime.InteropServices.JavaScript.JSObject; - #pragma warning disable CS0612 // using obsolete members until we finish https://github.com/dotnet/runtime/pull/66304/ namespace System @@ -75,10 +72,10 @@ namespace System } } - internal static class ConsolePal + internal static partial class ConsolePal { - private static volatile bool s_consoleInitialized; - private static JSObject? s_console; + [JSImport("globalThis.console.clear")] + public static partial void Clear(); private static Encoding? s_outputEncoding; @@ -169,17 +166,6 @@ namespace System char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor) => throw new PlatformNotSupportedException(); - public static void Clear() - { - if (!s_consoleInitialized) - { - s_console = (JSObject)System.Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("console"); - s_consoleInitialized = true; - } - - s_console?.Invoke("clear"); - } - public static void SetCursorPosition(int left, int top) => throw new PlatformNotSupportedException(); public static int BufferWidth diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 428ac78a8dd..a40be7c1c50 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -486,8 +486,11 @@ <Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" /> <Compile Include="System\Net\Http\BrowserHttpHandler\BrowserHttpHandler.cs" /> <Compile Include="System\Net\Http\BrowserHttpHandler\HttpTelemetry.Browser.cs" /> - <Compile Include="$(CommonPath)System\Net\Http\HttpHandlerDefaults.cs" + <Compile Include="System\Net\Http\BrowserHttpHandler\BrowserHttpInterop.cs" /> + <Compile Include="$(CommonPath)System\Net\Http\HttpHandlerDefaults.cs" Link="Common\System\Net\Http\HttpHandlerDefaults.cs" /> + <Compile Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" + Link="InteropServices\JavaScript\CancelablePromise.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="$(LibrariesProjectRoot)System.Net.Quic\src\System.Net.Quic.csproj" /> @@ -520,7 +523,8 @@ <Reference Include="System.Security.Cryptography" /> </ItemGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> - <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <Reference Include="System.Runtime.InteropServices.JavaScript" /> </ItemGroup> <ItemGroup Condition="'$(UseManagedNtlm)' == 'true'"> <ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" /> diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs index 9f484a74840..ccca155d4bc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs @@ -8,8 +8,6 @@ using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices.JavaScript; -#pragma warning disable CS0612 // using obsolete members until we finish https://github.com/dotnet/runtime/pull/66304/ - namespace System.Net.Http { // **Note** on `Task.ConfigureAwait(continueOnCapturedContext: true)` for the WebAssembly Browser. @@ -21,10 +19,6 @@ namespace System.Net.Http // we should put this in place now. internal sealed class BrowserHttpHandler : HttpMessageHandler { - // This partial implementation contains members common to Browser WebAssembly running on .NET Core. - private static readonly JSObject? s_fetch = (JSObject)Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("fetch"); - private static readonly JSObject? s_window = (JSObject)Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("window"); - private static readonly HttpRequestOptionsKey<bool> EnableStreamingResponse = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"); private static readonly HttpRequestOptionsKey<IDictionary<string, object>> FetchOptions = new HttpRequestOptionsKey<IDictionary<string, object>>("WebAssemblyFetchOptions"); private bool _allowAutoRedirect = HttpHandlerDefaults.DefaultAutomaticRedirection; @@ -34,17 +28,9 @@ namespace System.Net.Http /// <summary> /// Gets whether the current Browser supports streaming responses /// </summary> - private static bool StreamingSupported { get; } = GetIsStreamingSupported(); - private static bool GetIsStreamingSupported() - { - using (var streamingSupported = new Function("return typeof Response !== 'undefined' && 'body' in Response.prototype && typeof ReadableStream === 'function'")) - return (bool)streamingSupported.Call(); - } - - private static Function AbortControllerFactory = new Function("return new AbortController()"); - private static Function HeadersFactory = new Function("return new Headers()"); - private static Function ObjectFactory = new Function("return new Object()"); + private static bool StreamingSupported { get; } = BrowserHttpInterop.SupportsStreamingResponse(); + #region PlatformNotSupported #pragma warning disable CA1822 public bool UseCookies { @@ -118,6 +104,7 @@ namespace System.Net.Http set => throw new PlatformNotSupportedException(); } #pragma warning restore CA1822 + #endregion public bool AllowAutoRedirect { @@ -141,480 +128,343 @@ namespace System.Net.Http throw new PlatformNotSupportedException(); } - protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + private static async Task<WasmFetchResponse> CallFetch(HttpRequestMessage request, CancellationToken cancellationToken, bool? allowAutoRedirect) { - ArgumentNullException.ThrowIfNull(request); - return Impl(request, cancellationToken); - - async Task<HttpResponseMessage> Impl(HttpRequestMessage request, CancellationToken cancellationToken) + int headerCount = request.Headers.Count + request.Content?.Headers.Count ?? 0; + List<string> headerNames = new List<string>(headerCount); + List<string> headerValues = new List<string>(headerCount); + List<string> optionNames = new List<string>(); + List<object?> optionValues = new List<object?>(); + + JSObject abortController = BrowserHttpInterop.CreateAbortController(); + CancellationTokenRegistration? abortRegistration = cancellationToken.Register(() => { - CancellationTokenRegistration? abortRegistration = null; - try + if (!abortController.IsDisposed) { - using var requestObject = (JSObject)ObjectFactory.Call(); - - if (request.Options.TryGetValue(FetchOptions, out IDictionary<string, object>? fetchOptions)) - { - foreach (KeyValuePair<string, object> item in fetchOptions) - { - requestObject.SetObjectProperty(item.Key, item.Value); - } - } - - requestObject.SetObjectProperty("method", request.Method.Method); + BrowserHttpInterop.AbortRequest(abortController); + } + abortController.Dispose(); + }); + try + { + optionNames.Add("method"); + optionValues.Add(request.Method.Method); + if (allowAutoRedirect.HasValue) + { + optionNames.Add("redirect"); + optionValues.Add(allowAutoRedirect.Value ? "follow" : "manual"); + } - // Only set if property was specifically modified and is not default value - if (_isAllowAutoRedirectTouched) + foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers) + { + foreach (string value in header.Value) { - // Allowing or Disallowing redirects. - // Here we will set redirect to `manual` instead of error if AllowAutoRedirect is - // false so there is no exception thrown - // - // https://developer.mozilla.org/en-US/docs/Web/API/Response/type - // - // other issues from whatwg/fetch: - // - // https://github.com/whatwg/fetch/issues/763 - // https://github.com/whatwg/fetch/issues/601 - requestObject.SetObjectProperty("redirect", AllowAutoRedirect ? "follow" : "manual"); + headerNames.Add(header.Key); + headerValues.Add(value); } + } - // We need to check for body content - if (request.Content != null) + if (request.Content != null) + { + foreach (KeyValuePair<string, IEnumerable<string>> header in request.Content.Headers) { - if (request.Content is StringContent) + foreach (string value in header.Value) { - requestObject.SetObjectProperty("body", await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: true)); - } - else - { - using (Uint8Array uint8Buffer = Uint8Array.From(await request.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: true))) - { - requestObject.SetObjectProperty("body", uint8Buffer); - } + headerNames.Add(header.Key); + headerValues.Add(value); } } + } - // Process headers - // Cors has its own restrictions on headers. - // https://developer.mozilla.org/en-US/docs/Web/API/Headers - - using (JSObject jsHeaders = (JSObject)HeadersFactory.Call()) + if (request.Options.TryGetValue(FetchOptions, out IDictionary<string, object>? fetchOptions)) + { + foreach (KeyValuePair<string, object> item in fetchOptions) { - foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers) - { - foreach (string value in header.Value) - { - jsHeaders.Invoke("append", header.Key, value); - } - } - if (request.Content != null) - { - foreach (KeyValuePair<string, IEnumerable<string>> header in request.Content.Headers) - { - foreach (string value in header.Value) - { - jsHeaders.Invoke("append", header.Key, value); - } - } - } - requestObject.SetObjectProperty("headers", jsHeaders); + optionNames.Add(item.Key); + optionValues.Add(item.Value); } + } + if (request.RequestUri == null) + { + throw new ArgumentNullException(nameof(request.RequestUri)); + } - JSObject abortController = (JSObject)AbortControllerFactory.Call(); - using JSObject signal = (JSObject)abortController.GetObjectProperty("signal"); - requestObject.SetObjectProperty("signal", signal); - - abortRegistration = cancellationToken.Register(() => + string uri = request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString(); + Task<JSObject>? promise; + cancellationToken.ThrowIfCancellationRequested(); + if (request.Content != null) + { + if (request.Content is StringContent) { - if (!abortController.IsDisposed) - { - abortController.Invoke("abort"); - abortController?.Dispose(); - } - }); + string body = await request.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(true); + cancellationToken.ThrowIfCancellationRequested(); - using var args = new System.Runtime.InteropServices.JavaScript.Array(); - if (request.RequestUri != null) - { - args.Push(request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString()); - args.Push(requestObject); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, body); } - - - var responseTask = s_fetch?.Invoke("apply", s_window, args) as Task<object>; - if (responseTask == null) - throw new Exception(SR.net_http_marshalling_response_promise_from_fetch); - - cancellationToken.ThrowIfCancellationRequested(); - - var fetchResponseJs = (JSObject)await responseTask.ConfigureAwait(continueOnCapturedContext: true); - - var fetchResponse = new WasmFetchResponse(fetchResponseJs, abortController, abortRegistration.Value); - abortRegistration = null; - var responseMessage = new HttpResponseMessage((HttpStatusCode)fetchResponse.Status); - responseMessage.RequestMessage = request; - - // Here we will set the ReasonPhrase so that it can be evaluated later. - // We do not have a status code but this will signal some type of what happened - // after interrogating the status code for success or not i.e. IsSuccessStatusCode - // - // https://developer.mozilla.org/en-US/docs/Web/API/Response/type - // opaqueredirect: The fetch request was made with redirect: "manual". - // The Response's status is 0, headers are empty, body is null and trailer is empty. - if (fetchResponse.ResponseType == "opaqueredirect") + else { - responseMessage.SetReasonPhraseWithoutValidation(fetchResponse.ResponseType); - } + byte[] buffer = await request.Content.ReadAsByteArrayAsync(cancellationToken) + .ConfigureAwait(true); + cancellationToken.ThrowIfCancellationRequested(); - bool streamingEnabled = false; - if (StreamingSupported) - { - request.Options.TryGetValue(EnableStreamingResponse, out streamingEnabled); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, buffer); } - - responseMessage.Content = streamingEnabled - ? new StreamContent(new WasmHttpReadStream(fetchResponse)) - : new BrowserHttpContent(fetchResponse); - - // Fill the response headers - // CORS will only allow access to certain headers. - // If a request is made for a resource on another origin which returns the CORs headers, then the type is cors. - // cors and basic responses are almost identical except that a cors response restricts the headers you can view to - // `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`. - // View more information https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types - // - // Note: Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation - using (JSObject respHeaders = fetchResponse.Headers) - { - if (respHeaders != null) - { - using (var entriesIterator = (JSObject)respHeaders.Invoke("entries")) - { - JSObject? nextResult = null; - try - { - nextResult = (JSObject)entriesIterator.Invoke("next"); - while (!(bool)nextResult.GetObjectProperty("done")) - { - using (var resultValue = (System.Runtime.InteropServices.JavaScript.Array)nextResult.GetObjectProperty("value")) - { - var name = (string)resultValue[0]; - var value = (string)resultValue[1]; - if (!responseMessage.Headers.TryAddWithoutValidation(name, value)) - responseMessage.Content.Headers.TryAddWithoutValidation(name, value); - } - nextResult?.Dispose(); - nextResult = (JSObject)entriesIterator.Invoke("next"); - } - } - finally - { - nextResult?.Dispose(); - } - } - } - } - return responseMessage; - - } - catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) - { - throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); } - catch (JSException jse) + else { - throw TranslateJSException(jse, cancellationToken); - } - finally - { - abortRegistration?.Dispose(); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController); } + cancellationToken.ThrowIfCancellationRequested(); + ValueTask<JSObject> wrappedTask = BrowserHttpInterop.CancelationHelper(promise, cancellationToken, abortController); + JSObject fetchResponse = await wrappedTask.ConfigureAwait(true); + return new WasmFetchResponse(fetchResponse, abortRegistration.Value); + } + catch (Exception) + { + // this would also trigger abort + abortRegistration?.Dispose(); + throw; } } - private static Exception TranslateJSException(JSException jse, CancellationToken cancellationToken) + private static HttpResponseMessage ConvertResponse(HttpRequestMessage request, WasmFetchResponse fetchResponse) { - if (jse.Message.StartsWith("AbortError", StringComparison.Ordinal)) + string? responseType = fetchResponse.ResponseType; + HttpResponseMessage responseMessage = new HttpResponseMessage((HttpStatusCode)fetchResponse.Status); + responseMessage.RequestMessage = request; + if (responseType == "opaqueredirect") { - return CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None); + // Here we will set the ReasonPhrase so that it can be evaluated later. + // We do not have a status code but this will signal some type of what happened + // after interrogating the status code for success or not i.e. IsSuccessStatusCode + // + // https://developer.mozilla.org/en-US/docs/Web/API/Response/type + // opaqueredirect: The fetch request was made with redirect: "manual". + // The Response's status is 0, headers are empty, body is null and trailer is empty. + responseMessage.SetReasonPhraseWithoutValidation(fetchResponse.ResponseType); } - if (cancellationToken.IsCancellationRequested) + + bool streamingEnabled = false; + if (StreamingSupported) { - return CancellationHelper.CreateOperationCanceledException(jse, cancellationToken); + request.Options.TryGetValue(EnableStreamingResponse, out streamingEnabled); } - return new HttpRequestException(jse.Message, jse); + + responseMessage.Content = streamingEnabled + ? new StreamContent(new WasmHttpReadStream(fetchResponse)) + : new BrowserHttpContent(fetchResponse); + + + // Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation + // CORS will only allow access to certain headers on browser. + BrowserHttpInterop.GetResponseHeaders(fetchResponse.FetchResponse, responseMessage.Headers, responseMessage.Content.Headers); + + return responseMessage; } - private sealed class WasmFetchResponse : IDisposable + protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - private readonly JSObject _fetchResponse; - private readonly JSObject _abortController; - private readonly CancellationTokenRegistration _abortRegistration; - private bool _isDisposed; + ArgumentNullException.ThrowIfNull(request); + return Impl(request, cancellationToken, _isAllowAutoRedirectTouched ? AllowAutoRedirect : null); - public WasmFetchResponse(JSObject fetchResponse, JSObject abortController, CancellationTokenRegistration abortRegistration) + static async Task<HttpResponseMessage> Impl(HttpRequestMessage request, CancellationToken cancellationToken, bool? allowAutoRedirect) { - ArgumentNullException.ThrowIfNull(fetchResponse); - ArgumentNullException.ThrowIfNull(abortController); - - _fetchResponse = fetchResponse; - _abortController = abortController; - _abortRegistration = abortRegistration; + WasmFetchResponse fetchRespose = await CallFetch(request, cancellationToken, allowAutoRedirect).ConfigureAwait(true); + return ConvertResponse(request, fetchRespose); } + } + } - public bool IsOK => (bool)_fetchResponse.GetObjectProperty("ok"); - public bool IsRedirected => (bool)_fetchResponse.GetObjectProperty("redirected"); - public int Status => (int)_fetchResponse.GetObjectProperty("status"); - public string StatusText => (string)_fetchResponse.GetObjectProperty("statusText"); - public string ResponseType => (string)_fetchResponse.GetObjectProperty("type"); - public string Url => (string)_fetchResponse.GetObjectProperty("url"); - public bool IsBodyUsed => (bool)_fetchResponse.GetObjectProperty("bodyUsed"); - public JSObject Headers => (JSObject)_fetchResponse.GetObjectProperty("headers"); - public JSObject Body => (JSObject)_fetchResponse.GetObjectProperty("body"); - - public Task<object> ArrayBuffer() => (Task<object>)_fetchResponse.Invoke("arrayBuffer"); - public Task<object> Text() => (Task<object>)_fetchResponse.Invoke("text"); - public Task<object> JSON() => (Task<object>)_fetchResponse.Invoke("json"); - - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; + internal sealed class WasmFetchResponse : IDisposable + { + public readonly JSObject FetchResponse; + private readonly CancellationTokenRegistration _abortRegistration; + private bool _isDisposed; - _abortRegistration.Dispose(); + public WasmFetchResponse(JSObject fetchResponse, CancellationTokenRegistration abortRegistration) + { + ArgumentNullException.ThrowIfNull(fetchResponse); - _fetchResponse?.Dispose(); - if (_abortController != null && !_abortController.IsDisposed) - { - _abortController.Invoke("abort"); - } - _abortController?.Dispose(); - } + FetchResponse = fetchResponse; + _abortRegistration = abortRegistration; } - private sealed class BrowserHttpContent : HttpContent + public string ResponseType { - private byte[]? _data; - private readonly WasmFetchResponse _status; - - public BrowserHttpContent(WasmFetchResponse status) + get { - ArgumentNullException.ThrowIfNull(status); - - _status = status; + return FetchResponse.GetPropertyAsString("type")!; } + } - private async Task<byte[]> GetResponseData(CancellationToken cancellationToken) + public int Status + { + get { - if (_data != null) - { - return _data; - } - try - { - using (System.Runtime.InteropServices.JavaScript.ArrayBuffer dataBuffer = (System.Runtime.InteropServices.JavaScript.ArrayBuffer)await _status.ArrayBuffer().ConfigureAwait(continueOnCapturedContext: true)) - { - using (Uint8Array dataBinView = new Uint8Array(dataBuffer)) - { - _data = dataBinView.ToArray(); - _status.Dispose(); - } - } - } - catch (JSException jse) - { - throw TranslateJSException(jse, cancellationToken); - } - - return _data; + return FetchResponse.GetPropertyAsInt32("status"); } + } - protected override async Task<Stream> CreateContentReadStreamAsync() + public void ThrowIfDisposed() + { + if (_isDisposed && FetchResponse.IsDisposed) { - byte[] data = await GetResponseData(CancellationToken.None).ConfigureAwait(continueOnCapturedContext: true); - return new MemoryStream(data, writable: false); + throw new ObjectDisposedException(nameof(WasmFetchResponse)); } + } - protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => - SerializeToStreamAsync(stream, context, CancellationToken.None); - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) - { - byte[] data = await GetResponseData(cancellationToken).ConfigureAwait(continueOnCapturedContext: true); - await stream.WriteAsync(data, cancellationToken).ConfigureAwait(continueOnCapturedContext: true); - } - protected internal override bool TryComputeLength(out long length) - { - if (_data != null) - { - length = _data.Length; - return true; - } + public void Dispose() + { + if (_isDisposed) + return; - length = 0; - return false; - } + _isDisposed = true; + _abortRegistration.Dispose(); - protected override void Dispose(bool disposing) + if (FetchResponse != null && !FetchResponse.IsDisposed) { - _status?.Dispose(); - base.Dispose(disposing); + BrowserHttpInterop.AbortResponse(FetchResponse); } + FetchResponse?.Dispose(); } + } + + internal sealed class BrowserHttpContent : HttpContent + { + private byte[]? _data; + private int _length = -1; + private readonly WasmFetchResponse _fetchResponse; - private sealed class WasmHttpReadStream : Stream + public BrowserHttpContent(WasmFetchResponse fetchResponse) { - private WasmFetchResponse? _fetchResponse; - private JSObject? _reader; + ArgumentNullException.ThrowIfNull(fetchResponse); - private byte[]? _bufferedBytes; - private int _position; + _fetchResponse = fetchResponse; + } - public WasmHttpReadStream(WasmFetchResponse fetchResponse) + // TODO alocate smaller buffer and call multiple times + private async ValueTask<byte[]> GetResponseData(CancellationToken cancellationToken) + { + if (_data != null) { - _fetchResponse = fetchResponse; + return _data; } + _fetchResponse.ThrowIfDisposed(); + Task<int> promise = BrowserHttpInterop.GetResponseLength(_fetchResponse.FetchResponse); + _length = await BrowserHttpInterop.CancelationHelper(promise, cancellationToken, null, _fetchResponse.FetchResponse).ConfigureAwait(true); + _data = new byte[_length]; - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; - public override long Length => throw new NotSupportedException(); - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } + BrowserHttpInterop.GetResponseBytes(_fetchResponse.FetchResponse, new Span<byte>(_data)); - public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - ValidateBufferArguments(buffer, offset, count); - return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask(); - } + return _data; + } - public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) - { - CancellationHelper.ThrowIfCancellationRequested(cancellationToken); + protected override async Task<Stream> CreateContentReadStreamAsync() + { + byte[] data = await GetResponseData(CancellationToken.None).ConfigureAwait(true); + return new MemoryStream(data, writable: false); + } - if (_reader == null) - { - // If we've read everything, then _reader and _status will be null - if (_fetchResponse == null) - { - return 0; - } + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => + SerializeToStreamAsync(stream, context, CancellationToken.None); - try - { - using (JSObject body = _fetchResponse.Body) - { - _reader = (JSObject)body.Invoke("getReader"); - } - } - catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) - { - throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); - } - catch (JSException jse) - { - throw TranslateJSException(jse, cancellationToken); - } - } + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(stream, nameof(stream)); + byte[] data = await GetResponseData(cancellationToken).ConfigureAwait(true); + await stream.WriteAsync(data, cancellationToken).ConfigureAwait(true); + } - using var abortRegistration = cancellationToken.Register(() => - { - _reader.Invoke("cancel"); - }); + protected internal override bool TryComputeLength(out long length) + { + if (_length != -1) + { + length = _length; + return true; + } - if (_bufferedBytes != null && _position < _bufferedBytes.Length) - { - return ReadBuffered(); - } + length = 0; + return false; + } - try - { - var t = (Task<object>)_reader.Invoke("read"); - using (var read = (JSObject)await t.ConfigureAwait(continueOnCapturedContext: true)) - { - if (cancellationToken.IsCancellationRequested) - { - _reader.Invoke("cancel"); - throw CancellationHelper.CreateOperationCanceledException(null, cancellationToken); - } + protected override void Dispose(bool disposing) + { + _fetchResponse?.Dispose(); + base.Dispose(disposing); + } + } - if ((bool)read.GetObjectProperty("done")) - { - _reader.Dispose(); - _reader = null; + internal sealed class WasmHttpReadStream : Stream + { + private WasmFetchResponse _fetchResponse; - _fetchResponse?.Dispose(); - _fetchResponse = null; - return 0; - } + public WasmHttpReadStream(WasmFetchResponse fetchResponse) + { + _fetchResponse = fetchResponse; + } - _position = 0; - // value for fetch streams is a Uint8Array - using (Uint8Array binValue = (Uint8Array)read.GetObjectProperty("value")) - _bufferedBytes = binValue.ToArray(); - } - } - catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) - { - throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); - } - catch (JSException jse) - { - throw TranslateJSException(jse, cancellationToken); - } + public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(buffer, nameof(buffer)); - return ReadBuffered(); + _fetchResponse.ThrowIfDisposed(); + cancellationToken.ThrowIfCancellationRequested(); + using (Buffers.MemoryHandle handle = buffer.Pin()) + { + Task<int> promise = GetStreamedResponseBytesUnsafe(_fetchResponse, buffer, handle); + int response = await BrowserHttpInterop.CancelationHelper(promise, cancellationToken, null, _fetchResponse.FetchResponse).ConfigureAwait(true); + return response; + } - int ReadBuffered() - { - int n = Math.Min(_bufferedBytes.Length - _position, buffer.Length); - if (n <= 0) - { - return 0; - } + unsafe static Task<int> GetStreamedResponseBytesUnsafe(WasmFetchResponse _fetchResponse, Memory<byte> buffer, System.Buffers.MemoryHandle handle) + => BrowserHttpInterop.GetStreamedResponseBytes(_fetchResponse.FetchResponse, (IntPtr)handle.Pointer, buffer.Length); + } - _bufferedBytes.AsSpan(_position, n).CopyTo(buffer.Span); - _position += n; + public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateBufferArguments(buffer, offset, count); + return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask(); + } - return n; - } - } + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } - protected override void Dispose(bool disposing) - { - _reader?.Dispose(); - _fetchResponse?.Dispose(); - } + protected override void Dispose(bool disposing) + { + _fetchResponse?.Dispose(); + } - public override void Flush() - { - } + public override void Flush() + { + } - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(SR.net_http_synchronous_reads_not_supported); - } + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(SR.net_http_synchronous_reads_not_supported); + } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } - public override void SetLength(long value) - { - throw new NotSupportedException(); - } + public override void SetLength(long value) + { + throw new NotSupportedException(); + } - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpInterop.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpInterop.cs new file mode 100644 index 00000000000..0d6aec34548 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpInterop.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Net.Http.Headers; +using System.Runtime.InteropServices.JavaScript; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http +{ + internal static partial class BrowserHttpInterop + { + [JSImport("INTERNAL.http_wasm_supports_streaming_response")] + public static partial bool SupportsStreamingResponse(); + + [JSImport("INTERNAL.http_wasm_create_abort_controler")] + public static partial JSObject CreateAbortController(); + + [JSImport("INTERNAL.http_wasm_abort_request")] + public static partial void AbortRequest( + JSObject abortController); + + [JSImport("INTERNAL.http_wasm_abort_response")] + public static partial void AbortResponse( + JSObject fetchResponse); + + [JSImport("INTERNAL.http_wasm_get_response_header_names")] + private static partial string[] _GetResponseHeaderNames( + JSObject fetchResponse); + + [JSImport("INTERNAL.http_wasm_get_response_header_values")] + private static partial string[] _GetResponseHeaderValues( + JSObject fetchResponse); + + public static void GetResponseHeaders(JSObject fetchResponse, HttpHeaders resposeHeaders, HttpHeaders contentHeaders) + { + string[] headerNames = _GetResponseHeaderNames(fetchResponse); + string[] headerValues = _GetResponseHeaderValues(fetchResponse); + + for (int i = 0; i < headerNames.Length; i++) + { + if (!resposeHeaders.TryAddWithoutValidation(headerNames[i], headerValues[i])) + { + contentHeaders.TryAddWithoutValidation(headerNames[i], headerValues[i]); + } + } + } + + + [JSImport("INTERNAL.http_wasm_fetch")] + public static partial Task<JSObject> Fetch( + string uri, + string[] headerNames, + string[] headerValues, + string[] optionNames, + [JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues, + JSObject abortControler, + string? body = null); + + [JSImport("INTERNAL.http_wasm_fetch_bytes")] + private static partial Task<JSObject> FetchBytes( + string uri, + string[] headerNames, + string[] headerValues, + string[] optionNames, + [JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues, + JSObject abortControler, + IntPtr bodyPtr, + int bodyLength + ); + + public static unsafe Task<JSObject> Fetch(string uri, string[] headerNames, string[] headerValues, string[] optionNames, object?[] optionValues, JSObject abortControler, byte[] body) + { + fixed (byte* ptr = body) + { + return FetchBytes(uri, headerNames, headerValues, optionNames, optionValues, abortControler, (IntPtr)ptr, body.Length); + } + } + + [JSImport("INTERNAL.http_wasm_get_streamed_response_bytes")] + public static partial Task<int> GetStreamedResponseBytes( + JSObject fetchResponse, + IntPtr bufferPtr, + int bufferLength); + + [JSImport("INTERNAL.http_wasm_get_response_length")] + public static partial Task<int> GetResponseLength( + JSObject fetchResponse); + + [JSImport("INTERNAL.http_wasm_get_response_bytes")] + public static partial int GetResponseBytes( + JSObject fetchResponse, + [JSMarshalAs<JSType.MemoryView>] Span<byte> buffer); + + + public static async ValueTask<T> CancelationHelper<T>(Task<T> promise, CancellationToken cancellationToken, JSObject? abortController = null, JSObject? fetchResponse = null) + { + if (promise.IsCompletedSuccessfully) + { + return promise.Result; + } + try + { + using (var operationRegistration = cancellationToken.Register(() => + { + CancelablePromise.CancelPromise(promise); + if (abortController != null) + { + AbortRequest(abortController); + } + if (fetchResponse != null) + { + AbortResponse(fetchResponse); + } + + })) + { + return await promise.ConfigureAwait(true); + } + } + catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested) + { + throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken); + } + catch (JSException jse) + { + if (jse.Message.StartsWith("AbortError", StringComparison.Ordinal)) + { + throw CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None); + } + if (cancellationToken.IsCancellationRequested) + { + throw CancellationHelper.CreateOperationCanceledException(jse, cancellationToken); + } + throw new HttpRequestException(jse.Message, jse); + } + } + } + +} diff --git a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj index cff3d6e0041..f15169b1fdd 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj +++ b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj @@ -3,6 +3,10 @@ <AllowUnsafeBlocks>True</AllowUnsafeBlocks> <TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppCurrent)-Browser</TargetFrameworks> </PropertyGroup> + <PropertyGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> + <DefineConstants>$(DefineConstants);TARGET_BROWSER</DefineConstants> + </PropertyGroup> + <ItemGroup> <Compile Include="System\Net\WebSockets\ClientWebSocket.cs" /> <Compile Include="System\Net\WebSockets\ClientWebSocketDeflateConstants.cs" /> @@ -16,8 +20,10 @@ </ItemGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> <Compile Include="System\Net\WebSockets\WebSocketHandle.Browser.cs" /> + <Compile Include="System\Net\WebSockets\BrowserWebSockets\BrowserInterop.cs" /> <Compile Include="System\Net\WebSockets\BrowserWebSockets\BrowserWebSocket.cs" /> <Compile Include="System\Net\WebSockets\BrowserWebSockets\ClientWebSocketOptions.cs" /> + <Compile Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" Link="InteropServices\JavaScript\CancelablePromise.cs" /> </ItemGroup> <ItemGroup> <Reference Include="Microsoft.Win32.Primitives" /> @@ -37,6 +43,7 @@ <Reference Include="System.Memory" /> </ItemGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> - <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <Reference Include="System.Runtime.InteropServices.JavaScript" /> </ItemGroup> </Project> diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs new file mode 100644 index 00000000000..4e16d14aff3 --- /dev/null +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.InteropServices; + +namespace System.Net.WebSockets +{ + internal static partial class BrowserInterop + { + public static string? GetProtocol(JSObject? webSocket) + { + if (webSocket == null || webSocket.IsDisposed) return null; + string? protocol = webSocket.GetPropertyAsString("protocol"); + return protocol; + } + + public static int GetReadyState(JSObject? webSocket) + { + if (webSocket == null || webSocket.IsDisposed) return -1; + int? readyState = webSocket.GetPropertyAsInt32("readyState"); + if (!readyState.HasValue) return -1; + return readyState.Value; + } + + [JSImport("INTERNAL.ws_wasm_create")] + public static partial JSObject WebSocketCreate( + string uri, + string?[]? subProtocols, + [JSMarshalAs<JSType.Function<JSType.Number, JSType.String>>] Action<int, string> onClosed); + + [JSImport("INTERNAL.ws_wasm_open")] + public static partial Task WebSocketOpen( + JSObject webSocket); + + [JSImport("INTERNAL.ws_wasm_send")] + public static partial Task? WebSocketSend( + JSObject webSocket, + [JSMarshalAs<JSType.MemoryView>] ArraySegment<byte> buffer, + int messageType, + bool endOfMessage); + + [JSImport("INTERNAL.ws_wasm_receive")] + public static partial Task? WebSocketReceive( + JSObject webSocket, + [JSMarshalAs<JSType.MemoryView>] ArraySegment<byte> buffer, + [JSMarshalAs<JSType.MemoryView>] ArraySegment<int> response); + + [JSImport("INTERNAL.ws_wasm_close")] + public static partial Task? WebSocketClose( + JSObject webSocket, + int code, + string? reason, + bool waitForCloseReceived); + + [JSImport("INTERNAL.ws_wasm_abort")] + public static partial void WebSocketAbort( + JSObject webSocket); + + } +} diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs index a4d6a2c53ab..2fea50537a8 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs @@ -6,11 +6,6 @@ using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices.JavaScript; -using JavaScript = System.Runtime.InteropServices.JavaScript; -using JSObject = System.Runtime.InteropServices.JavaScript.JSObject; - -#pragma warning disable CS0612 // using obsolete members until we finish https://github.com/dotnet/runtime/pull/66304/ - namespace System.Net.WebSockets { /// <summary> @@ -41,7 +36,7 @@ namespace System.Net.WebSockets public override WebSocketCloseStatus? CloseStatus => _closeStatus; public override string? CloseStatusDescription => _closeStatusDescription; - public override string? SubProtocol => _innerWebSocket != null && !_innerWebSocket.IsDisposed ? _innerWebSocket!.GetObjectProperty("protocol")?.ToString() : null; + public override string? SubProtocol => BrowserInterop.GetProtocol(_innerWebSocket); #endregion Properties @@ -146,7 +141,7 @@ namespace System.Net.WebSockets _aborted = true; if (_innerWebSocket != null) { - JavaScript.Runtime.WebSocketAbort(_innerWebSocket!); + BrowserInterop.WebSocketAbort(_innerWebSocket!); } } } @@ -174,7 +169,7 @@ namespace System.Net.WebSockets { try { - object[]? subProtocols = requestedSubProtocols?.ToArray(); + string[]? subProtocols = requestedSubProtocols?.ToArray(); var onClose = (int code, string reason) => { _closeStatus = (WebSocketCloseStatus)code; @@ -186,8 +181,9 @@ namespace System.Net.WebSockets } }; - var openTask = JavaScript.Runtime.WebSocketOpen(uri.ToString(), subProtocols, onClose, out _innerWebSocket, out IntPtr promiseJSHandle); - var wrappedTask = CancelationHelper(openTask, promiseJSHandle, cancellationToken, _state); + _innerWebSocket = BrowserInterop.WebSocketCreate(uri.ToString(), subProtocols, onClose); + var openTask = BrowserInterop.WebSocketOpen(_innerWebSocket); + var wrappedTask = CancelationHelper(openTask!, cancellationToken, _state); await wrappedTask.ConfigureAwait(true); if (State == WebSocketState.Connecting) @@ -215,13 +211,13 @@ namespace System.Net.WebSockets { try { - var sendTask = JavaScript.Runtime.WebSocketSend(_innerWebSocket!, buffer, (int)messageType, endOfMessage, out IntPtr promiseJSHandle); + var sendTask = BrowserInterop.WebSocketSend(_innerWebSocket!, buffer, (int)messageType, endOfMessage); if (sendTask == null) { // return synchronously return; } - var wrappedTask = CancelationHelper(sendTask, promiseJSHandle, cancellationToken, _state); + var wrappedTask = CancelationHelper(sendTask, cancellationToken, _state); await wrappedTask.ConfigureAwait(true); } @@ -244,14 +240,14 @@ namespace System.Net.WebSockets try { ArraySegment<int> response = new ArraySegment<int>(new int[3]); - var receiveTask = JavaScript.Runtime.WebSocketReceive(_innerWebSocket!, buffer, response, out IntPtr promiseJSHandle); + var receiveTask = BrowserInterop.WebSocketReceive(_innerWebSocket!, buffer, response); if (receiveTask == null) { // return synchronously return ConvertResponse(response); } - var wrappedTask = CancelationHelper(receiveTask, promiseJSHandle, cancellationToken, _state); + var wrappedTask = CancelationHelper(receiveTask, cancellationToken, _state); await wrappedTask.ConfigureAwait(true); return ConvertResponse(response); @@ -289,10 +285,10 @@ namespace System.Net.WebSockets _closeStatus = closeStatus; _closeStatusDescription = statusDescription; - var closeTask = JavaScript.Runtime.WebSocketClose(_innerWebSocket!, (int)closeStatus, statusDescription, waitForCloseReceived, out IntPtr promiseJSHandle); + var closeTask = BrowserInterop.WebSocketClose(_innerWebSocket!, (int)closeStatus, statusDescription, waitForCloseReceived); if (closeTask != null) { - var wrappedTask = CancelationHelper(closeTask, promiseJSHandle, cancellationToken, _state); + var wrappedTask = CancelationHelper(closeTask, cancellationToken, _state); await wrappedTask.ConfigureAwait(true); } @@ -303,24 +299,21 @@ namespace System.Net.WebSockets } } - private async ValueTask<object> CancelationHelper(Task<object> jsTask, IntPtr promiseJSHandle, CancellationToken cancellationToken, WebSocketState previousState) + private async ValueTask CancelationHelper(Task jsTask, CancellationToken cancellationToken, WebSocketState previousState) { if (jsTask.IsCompletedSuccessfully) { - return jsTask.Result; + return; } try { using (var receiveRegistration = cancellationToken.Register(() => { - // this check makes sure that promiseJSHandle is still valid handle - if (!jsTask.IsCompleted) - { - JavaScript.Runtime.CancelPromise(promiseJSHandle); - } + CancelablePromise.CancelPromise(jsTask); })) { - return await jsTask.ConfigureAwait(true); + await jsTask.ConfigureAwait(true); + return; } } catch (JSException ex) @@ -355,7 +348,7 @@ namespace System.Net.WebSockets private WebSocketState GetReadyState() { - int readyState = (int)_innerWebSocket!.GetObjectProperty("readyState"); + int readyState = BrowserInterop.GetReadyState(_innerWebSocket); // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState return readyState switch diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.TypeForwards.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.TypeForwards.cs deleted file mode 100644 index 381bb4d7e64..00000000000 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.TypeForwards.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma warning disable CS0612 // using obsolete members until we finish https://github.com/dotnet/runtime/pull/66304/ - -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.Array))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.ArrayBuffer))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.DataView))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.Function))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.Uint8Array))] - -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.Runtime))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.JSObject))] -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.InteropServices.JavaScript.JSException))] diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj deleted file mode 100644 index 1660560c24f..00000000000 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj +++ /dev/null @@ -1,13 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <TargetFramework>$(NetCoreAppCurrent)-Browser</TargetFramework> - </PropertyGroup> - <ItemGroup> - <Reference Include="System.Runtime" /> - <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/> - </ItemGroup> - <ItemGroup> - <Compile Include="System.Private.Runtime.InteropServices.JavaScript.TypeForwards.cs" /> - </ItemGroup> -</Project> diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj index c99e34b5316..260e965efe4 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj @@ -18,14 +18,14 @@ <Compile Include="System\Runtime\InteropServices\JavaScript\Http\HttpRequestMessageTest.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\ParallelTests.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" /> + <Compile Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\tests\System\Runtime\InteropServices\JavaScript\Utils.cs" Link="System\Runtime\InteropServices\JavaScript\Utils.cs" /> </ItemGroup> <ItemGroup> - <WasmExtraFilesToDeploy Include="timers.js" /> - <None Include="timers.js" /> - </ItemGroup> - <ItemGroup> - <!-- Part of the shared framework but not exposed. --> + <WasmExtraFilesToDeploy Include="timers.mjs" /> + <None Include="timers.mjs" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> </Project> diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/TimerTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/TimerTests.cs index 2bf8dee0b82..3a12086a93a 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/TimerTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/TimerTests.cs @@ -13,13 +13,6 @@ namespace System.Runtime.InteropServices.JavaScript.Tests [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowserDomSupported))] public class TimerTests : IAsyncLifetime { - static JSObject _timersHelper; - static Function _installWrapper; - static Function _getRegisterCount; - static Function _getHitCount; - static Function _cleanupWrapper; - static Function _log; - public static IEnumerable<object[]> TestCases() { yield return new object[] { new int[0], 0, null, null }; @@ -37,24 +30,24 @@ namespace System.Runtime.InteropServices.JavaScript.Tests Timer[] timers = new Timer[timeouts.Length]; try { - _log.Call(_timersHelper, $"Waiting for runtime to settle"); + TimersJS.Log($"Waiting for runtime to settle"); // the test is quite sensitive to timing and order of execution. Here we are giving time to timers of XHarness and previous tests to finish. await Task.Delay(2000); - _installWrapper.Call(_timersHelper); - _log.Call(_timersHelper, $"Ready!"); + TimersJS.Install(); + TimersJS.Log($"Ready!"); for (int i = 0; i < timeouts.Length; i++) { int index = i; - _log.Call(_timersHelper, $"Registering {index} delay {timeouts[i]}"); + TimersJS.Log($"Registering {index} delay {timeouts[i]}"); timers[i] = new Timer((_) => { - _log.Call(_timersHelper, $"In timer{index}"); + TimersJS.Log($"In timer{index}"); wasCalled++; }, null, timeouts[i], 0); } - var setCounter = (int)_getRegisterCount.Call(_timersHelper); + var setCounter = TimersJS.GetRegisterCount(); Assert.True(0 == wasCalled, $"wasCalled: {wasCalled}"); Assert.True((expectedSetCounter ?? timeouts.Length) == setCounter, $"setCounter: actual {setCounter} expected {expectedSetCounter}"); @@ -65,23 +58,23 @@ namespace System.Runtime.InteropServices.JavaScript.Tests // Here we are giving time to our timers to finish. var afterLastTimer = timeouts.Length == 0 ? 500 : 500 + timeouts.Max(); - _log.Call(_timersHelper, "wait for timers to run"); + TimersJS.Log("wait for timers to run"); // this delay is also implemented as timer, so it counts to asserts await Task.Delay(afterLastTimer); - _log.Call(_timersHelper, "cleanup"); - _cleanupWrapper.Call(_timersHelper); + TimersJS.Log("cleanup"); + TimersJS.Cleanup(); Assert.True(timeouts.Length == wasCalled, $"wasCalled: actual {wasCalled} expected {timeouts.Length}"); if (expectedSetCounterAfterCleanUp != null) { - var setCounter = (int)_getRegisterCount.Call(_timersHelper); + var setCounter = TimersJS.GetRegisterCount(); Assert.True(expectedSetCounterAfterCleanUp.Value == setCounter, $"setCounter: actual {setCounter} expected {expectedSetCounterAfterCleanUp.Value}"); } if (expectedHitCount != null) { - var hitCounter = (int)_getHitCount.Call(_timersHelper); + var hitCounter = TimersJS.GetHitCount(); Assert.True(expectedHitCount == hitCounter, $"hitCounter: actual {hitCounter} expected {expectedHitCount}"); } @@ -92,27 +85,34 @@ namespace System.Runtime.InteropServices.JavaScript.Tests } } + static JSObject _module; public async Task InitializeAsync() { - if (_timersHelper == null) + if (_module == null) { - Function helper = new Function(@" - const loadTimersJs = async () => { - await import('./timers.js'); - }; - return loadTimersJs(); - "); - await (Task)helper.Call(_timersHelper); - - _timersHelper = (JSObject)Runtime.GetGlobalObject("timersHelper"); - _installWrapper = (Function)_timersHelper.GetObjectProperty("install"); - _getRegisterCount = (Function)_timersHelper.GetObjectProperty("getRegisterCount"); - _getHitCount = (Function)_timersHelper.GetObjectProperty("getHitCount"); - _cleanupWrapper = (Function)_timersHelper.GetObjectProperty("cleanup"); - _log = (Function)_timersHelper.GetObjectProperty("log"); + _module = await JSHost.ImportAsync("Timers", "./timers.mjs"); } } public Task DisposeAsync() => Task.CompletedTask; } + + public static partial class TimersJS + { + [JSImport("log", "Timers")] + public static partial void Log(string message); + + [JSImport("install", "Timers")] + public static partial void Install(); + + [JSImport("cleanup", "Timers")] + public static partial void Cleanup(); + + [JSImport("getRegisterCount", "Timers")] + public static partial int GetRegisterCount(); + + [JSImport("getHitCount", "Timers")] + public static partial int GetHitCount(); + + } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.js b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.js deleted file mode 100644 index ca2b7f31d52..00000000000 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.js +++ /dev/null @@ -1,47 +0,0 @@ -class TimersHelper { - log(message) { - // uncomment for debugging - // console.log(message); - } - install() { - const measuredCallbackName = "mono_wasm_set_timeout_exec"; - globalThis.registerCount = 0; - globalThis.hitCount = 0; - this.log("install") - if (!globalThis.originalSetTimeout) { - globalThis.originalSetTimeout = globalThis.setTimeout; - } - globalThis.setTimeout = (cb, time) => { - var start = Date.now().valueOf(); - if (cb.name === measuredCallbackName) { - globalThis.registerCount++; - this.log(`registerCount: ${globalThis.registerCount} now:${start} delay:${time}`) - } - return globalThis.originalSetTimeout(() => { - if (cb.name === measuredCallbackName) { - var hit = Date.now().valueOf(); - globalThis.hitCount++; - this.log(`hitCount: ${globalThis.hitCount} now:${hit} delay:${time} delta:${hit - start}`) - } - cb(); - }, time); - }; - } - - getRegisterCount() { - this.log(`registerCount: ${globalThis.registerCount} `) - return globalThis.registerCount; - } - - getHitCount() { - this.log(`hitCount: ${globalThis.hitCount} `) - return globalThis.hitCount; - } - - cleanup() { - this.log(`cleanup registerCount: ${globalThis.registerCount} hitCount: ${globalThis.hitCount} `) - globalThis.setTimeout = globalThis.originalSetTimeout; - } -} - -globalThis.timersHelper = new TimersHelper(); diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.mjs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.mjs new file mode 100644 index 00000000000..4e471c397c2 --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/timers.mjs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +export function log(message) { + // uncomment for debugging + console.log(message); +} + +export function install() { + const measuredCallbackName = "mono_wasm_set_timeout_exec"; + globalThis.registerCount = 0; + globalThis.hitCount = 0; + log("install") + if (!globalThis.originalSetTimeout) { + globalThis.originalSetTimeout = globalThis.setTimeout; + } + globalThis.setTimeout = (cb, time) => { + var start = Date.now().valueOf(); + if (cb.name === measuredCallbackName) { + globalThis.registerCount++; + log(`registerCount: ${globalThis.registerCount} now:${start} delay:${time}`) + } + return globalThis.originalSetTimeout(() => { + if (cb.name === measuredCallbackName) { + var hit = Date.now().valueOf(); + globalThis.hitCount++; + log(`hitCount: ${globalThis.hitCount} now:${hit} delay:${time} delta:${hit - start}`) + } + cb(); + }, time); + }; +} + +export function getRegisterCount() { + log(`registerCount: ${globalThis.registerCount} `) + return globalThis.registerCount; +} + +export function getHitCount() { + log(`hitCount: ${globalThis.hitCount} `) + return globalThis.hitCount; +} + +export function cleanup() { + log(`cleanup registerCount: ${globalThis.registerCount} hitCount: ${globalThis.hitCount} `) + globalThis.setTimeout = globalThis.originalSetTimeout; +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln index 15cd8ab85c1..8676c09e39f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServi EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JSImportGenerator", "gen\JSImportGenerator\JSImportGenerator.csproj", "{CE5E53C1-F9B5-41EE-8D00-837913EC57D1}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServices", "..\System.Runtime.InteropServices\ref\System.Runtime.InteropServices.csproj", "{6B7FDECD-61C2-4764-A994-AB117B405E33}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServices", "..\System.Runtime.InteropServices\src\System.Runtime.InteropServices.csproj", "{9BEEB101-BBD6-45D0-9B50-819EDB5FF3D3}" @@ -29,7 +31,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CoreLib.Generators", "..\System.Private.CoreLib\gen\System.Private.CoreLib.Generators.csproj", "{3355B50F-2794-4BD5-8D03-AF54CDB7DF98}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryImportGenerator", "..\System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj", "{BD7963AE-23CA-4F52-9500-9C67F1F5B42D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServices.JavaScript.Tests", "tests\System.Runtime.InteropServices.JavaScript.Tests.csproj", "{765B4AA5-723A-44FF-BC4E-EB0F03103F6D}" + ProjectSection(ProjectDependencies) = postProject + {3355B50F-2794-4BD5-8D03-AF54CDB7DF98} = {3355B50F-2794-4BD5-8D03-AF54CDB7DF98} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ancillary.Interop", "..\System.Runtime.InteropServices\tests\Ancillary.Interop\Ancillary.Interop.csproj", "{EC3ADEFA-1FF3-482C-8CCB-FE4C77292532}" EndProject @@ -37,6 +42,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Clien ProjectSection(ProjectDependencies) = postProject {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} {AD1E9074-194E-478A-B43F-BEC704EAFD11} = {AD1E9074-194E-478A-B43F-BEC704EAFD11} + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} EndProjectSection EndProject @@ -50,14 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Clien EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CoreLib", "..\..\mono\System.Private.CoreLib\System.Private.CoreLib.csproj", "{C8FB882D-DB06-4F4B-BCFB-5B7F936E127B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.Runtime.InteropServices.JavaScript", "..\System.Private.Runtime.InteropServices.JavaScript\src\System.Private.Runtime.InteropServices.JavaScript.csproj", "{963B6EB8-13FE-44EB-9B02-762A8AB9C459}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryImportGenerator.Unit.Tests", "..\System.Runtime.InteropServices\tests\LibraryImportGenerator.UnitTests\LibraryImportGenerator.Unit.Tests.csproj", "{518D3BDB-323D-43EB-B868-A7EDC08A15F5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JSImportGenerator.Unit.Tests", "tests\JSImportGenerator.UnitTest\JSImportGenerator.Unit.Tests.csproj", "{BFED925C-18F2-4C98-833E-66F205234598}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JSImportGenerator", "gen\JSImportGenerator\JSImportGenerator.csproj", "{BE06AA07-F96F-4B63-8445-AFBC688D5CE0}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Checked|Any CPU = Checked|Any CPU @@ -182,6 +182,48 @@ Global {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45}.Release|x64.Build.0 = Release|Any CPU {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45}.Release|x86.ActiveCfg = Release|Any CPU {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45}.Release|x86.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|Any CPU.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|arm.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|arm64.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|armv6.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|armv6.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|s390x.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|s390x.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|wasm.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|wasm.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|x64.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|x64.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|x86.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Checked|x86.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|arm.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|arm64.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|armv6.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|armv6.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|s390x.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|s390x.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|wasm.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|wasm.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|x64.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Debug|x86.Build.0 = Debug|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|Any CPU.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|arm.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|arm64.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|armv6.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|armv6.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|s390x.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|s390x.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|wasm.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|wasm.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|x64.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|x64.Build.0 = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|x86.ActiveCfg = Release|Any CPU + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1}.Release|x86.Build.0 = Release|Any CPU {6B7FDECD-61C2-4764-A994-AB117B405E33}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {6B7FDECD-61C2-4764-A994-AB117B405E33}.Checked|Any CPU.Build.0 = Debug|Any CPU {6B7FDECD-61C2-4764-A994-AB117B405E33}.Checked|arm.ActiveCfg = Debug|Any CPU @@ -509,54 +551,54 @@ Global {3355B50F-2794-4BD5-8D03-AF54CDB7DF98}.Release|x64.Build.0 = Release|Any CPU {3355B50F-2794-4BD5-8D03-AF54CDB7DF98}.Release|x86.ActiveCfg = Release|Any CPU {3355B50F-2794-4BD5-8D03-AF54CDB7DF98}.Release|x86.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|Any CPU.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|arm.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|arm.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|arm64.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|arm64.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|armv6.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|armv6.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|s390x.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|s390x.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|wasm.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|wasm.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|x64.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|x64.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|x86.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Checked|x86.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|arm.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|arm.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|arm64.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|arm64.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|armv6.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|armv6.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|s390x.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|s390x.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|wasm.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|wasm.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|x64.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|x64.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|x86.ActiveCfg = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Debug|x86.Build.0 = Debug|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|Any CPU.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|arm.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|arm.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|arm64.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|arm64.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|armv6.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|armv6.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|s390x.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|s390x.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|wasm.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|wasm.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|x64.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|x64.Build.0 = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|x86.ActiveCfg = Release|Any CPU - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D}.Release|x86.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|Any CPU.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|arm.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|arm.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|arm64.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|arm64.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|armv6.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|armv6.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|s390x.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|s390x.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|wasm.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|wasm.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|x64.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|x64.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|x86.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Checked|x86.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|arm.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|arm.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|arm64.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|arm64.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|armv6.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|armv6.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|s390x.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|s390x.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|wasm.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|wasm.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|x64.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|x64.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|x86.ActiveCfg = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Debug|x86.Build.0 = Debug|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|Any CPU.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|arm.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|arm.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|arm64.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|arm64.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|armv6.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|armv6.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|s390x.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|s390x.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|wasm.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|wasm.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|x64.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|x64.Build.0 = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|x86.ActiveCfg = Release|Any CPU + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D}.Release|x86.Build.0 = Release|Any CPU {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532}.Checked|Any CPU.Build.0 = Debug|Any CPU {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532}.Checked|arm.ActiveCfg = Debug|Any CPU @@ -866,102 +908,6 @@ Global {C8FB882D-DB06-4F4B-BCFB-5B7F936E127B}.Release|x64.Build.0 = Release|x64 {C8FB882D-DB06-4F4B-BCFB-5B7F936E127B}.Release|x86.ActiveCfg = Release|x86 {C8FB882D-DB06-4F4B-BCFB-5B7F936E127B}.Release|x86.Build.0 = Release|x86 - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|Any CPU.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|arm.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|arm.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|arm64.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|arm64.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|armv6.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|armv6.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|s390x.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|s390x.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|wasm.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|wasm.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|x64.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|x64.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|x86.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Checked|x86.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|Any CPU.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|arm.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|arm.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|arm64.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|arm64.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|armv6.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|armv6.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|s390x.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|s390x.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|wasm.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|wasm.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|x64.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|x64.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|x86.ActiveCfg = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Debug|x86.Build.0 = Debug|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|Any CPU.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|Any CPU.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|arm.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|arm.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|arm64.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|arm64.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|armv6.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|armv6.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|s390x.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|s390x.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|wasm.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|wasm.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|x64.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|x64.Build.0 = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|x86.ActiveCfg = Release|Any CPU - {963B6EB8-13FE-44EB-9B02-762A8AB9C459}.Release|x86.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|Any CPU.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|arm.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|arm.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|arm64.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|arm64.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|armv6.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|armv6.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|s390x.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|s390x.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|wasm.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|wasm.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|x64.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|x64.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|x86.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Checked|x86.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|arm.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|arm.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|arm64.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|arm64.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|armv6.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|armv6.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|s390x.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|s390x.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|wasm.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|wasm.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|x64.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|x64.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|x86.ActiveCfg = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Debug|x86.Build.0 = Debug|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|Any CPU.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|arm.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|arm.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|arm64.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|arm64.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|armv6.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|armv6.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|s390x.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|s390x.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|wasm.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|wasm.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|x64.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|x64.Build.0 = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|x86.ActiveCfg = Release|Any CPU - {518D3BDB-323D-43EB-B868-A7EDC08A15F5}.Release|x86.Build.0 = Release|Any CPU {BFED925C-18F2-4C98-833E-66F205234598}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {BFED925C-18F2-4C98-833E-66F205234598}.Checked|Any CPU.Build.0 = Debug|Any CPU {BFED925C-18F2-4C98-833E-66F205234598}.Checked|arm.ActiveCfg = Debug|Any CPU @@ -1010,54 +956,6 @@ Global {BFED925C-18F2-4C98-833E-66F205234598}.Release|x64.Build.0 = Release|Any CPU {BFED925C-18F2-4C98-833E-66F205234598}.Release|x86.ActiveCfg = Release|Any CPU {BFED925C-18F2-4C98-833E-66F205234598}.Release|x86.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|Any CPU.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|arm.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|arm.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|arm64.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|arm64.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|armv6.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|armv6.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|s390x.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|s390x.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|wasm.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|wasm.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|x64.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|x64.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|x86.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Checked|x86.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|arm.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|arm.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|arm64.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|arm64.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|armv6.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|armv6.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|s390x.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|s390x.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|wasm.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|wasm.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|x64.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|x64.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|x86.ActiveCfg = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Debug|x86.Build.0 = Debug|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|Any CPU.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|arm.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|arm.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|arm64.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|arm64.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|armv6.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|armv6.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|s390x.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|s390x.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|wasm.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|wasm.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|x64.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|x64.Build.0 = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|x86.ActiveCfg = Release|Any CPU - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1065,6 +963,7 @@ Global GlobalSection(NestedProjects) = preSolution {28278E01-BF5C-4AB6-AA7A-8DD4E6C04DB1} = {C22C479B-769A-4859-B974-E9B9D65918DE} {71A845ED-4344-41FC-8FCA-3AC9B6BA6C45} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} + {CE5E53C1-F9B5-41EE-8D00-837913EC57D1} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} {6B7FDECD-61C2-4764-A994-AB117B405E33} = {C22C479B-769A-4859-B974-E9B9D65918DE} {9BEEB101-BBD6-45D0-9B50-819EDB5FF3D3} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} {B4E3E774-2C16-4CBF-87EF-88C547529B94} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} @@ -1072,7 +971,7 @@ Global {ED86AB26-1CFB-457D-BF87-B7A0D8FAF272} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {0BBBDBE2-423D-48EB-8B6D-BF1140B4DF5A} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {3355B50F-2794-4BD5-8D03-AF54CDB7DF98} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} - {BD7963AE-23CA-4F52-9500-9C67F1F5B42D} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} + {765B4AA5-723A-44FF-BC4E-EB0F03103F6D} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {EC3ADEFA-1FF3-482C-8CCB-FE4C77292532} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {F4D8364B-F523-4E70-9273-D5E7393DCC99} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} {802982C1-94A4-47E4-9E5E-351A9721FFF0} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} @@ -1080,10 +979,7 @@ Global {18E039DE-1A5F-447C-90DE-4385B97CE566} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {32E55B8B-06CA-4FB0-BA58-67314FB6BC15} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {C8FB882D-DB06-4F4B-BCFB-5B7F936E127B} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} - {963B6EB8-13FE-44EB-9B02-762A8AB9C459} = {7973EAA3-43B6-4D78-B24C-38BA6BC0D1E3} - {518D3BDB-323D-43EB-B868-A7EDC08A15F5} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} {BFED925C-18F2-4C98-833E-66F205234598} = {26A72FFB-871A-4F2F-A513-B2F6E09F358C} - {BE06AA07-F96F-4B63-8445-AFBC688D5CE0} = {F2C2C78A-CEDD-4DE0-9C3A-0195F00E0B4E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FE64246-4AFA-424A-AE5D-7007E20451B5} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 93265dc706e..0f1d6e8058e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -3,8 +3,6 @@ <TargetFrameworks>$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)</TargetFrameworks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Nullable>enable</Nullable> - <!-- Use following lines to write the generated files to disk. --> - <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <RootNamespace></RootNamespace> </PropertyGroup> @@ -28,6 +26,7 @@ </ItemGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> <Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="System\Runtime\InteropServices\JavaScript\Interop\Interop.Runtime.cs" /> + <Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptImports.Generated.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptExports.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\Interop\JavaScriptImports.cs" /> @@ -51,6 +50,7 @@ <Compile Include="System\Runtime\InteropServices\JavaScript\JSException.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\JSExportAttribute.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" /> + <Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" /> <Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" /> @@ -72,4 +72,7 @@ <Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.Exception.cs" /> </ItemGroup> + <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'"> + <AnalyzerReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" Pack="true" ReferenceAnalyzer="true" /> + </ItemGroup> </Project> diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs new file mode 100644 index 00000000000..391f692e0ba --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CancelablePromise.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +using System.Threading.Tasks; + +namespace System.Runtime.InteropServices.JavaScript +{ + internal static partial class CancelablePromise + { + [JSImport("INTERNAL.mono_wasm_cancel_promise")] + private static partial void _CancelPromise(IntPtr promiseGCHandle); + + public static void CancelPromise(Task promise) + { + // this check makes sure that promiseGCHandle is still valid handle + if (promise.IsCompleted) + { + return; + } + GCHandle? promiseGCHandle = promise.AsyncState as GCHandle?; + if (promiseGCHandle == null) throw new InvalidOperationException("Expected Task converted from JS Promise"); + + _CancelPromise((IntPtr)promiseGCHandle.Value); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 305caf4b729..619dec1a5f3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -1,13 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace System.Runtime.InteropServices.JavaScript { + // this maps to src\mono\wasm\runtime\corebindings.c + // the methods are protected from trimming by DynamicDependency on JSFunctionBinding internal static unsafe partial class JavaScriptExports { + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void GetCSOwnedObjectByJSHandleRef(IntPtr jsHandle, int shouldAddInflight, out JSObject? result) { lock (JSHostImplementation.s_csOwnedObjects) @@ -26,6 +31,7 @@ namespace System.Runtime.InteropServices.JavaScript result = null; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static IntPtr TryGetCSOwnedObjectJSHandleRef(in object rawObj, int shouldAddInflight) { JSObject? jsObject = rawObj as JSObject; @@ -36,18 +42,19 @@ namespace System.Runtime.InteropServices.JavaScript return jsObject?.JSHandle ?? IntPtr.Zero; } - public static void CreateCSOwnedProxyRef(IntPtr jsHandle, JSHostImplementation.MappedType mappedType, int shouldAddInflight, out JSObject? jsObject) + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + public static void CreateCSOwnedProxyRef(IntPtr jsHandle, JSHostImplementation.MappedType mappedType, int shouldAddInflight, out JSObject jsObject) { - jsObject = null; + JSObject? res = null; lock (JSHostImplementation.s_csOwnedObjects) { if (!JSHostImplementation.s_csOwnedObjects.TryGetValue((int)jsHandle, out WeakReference<JSObject>? reference) || - !reference.TryGetTarget(out jsObject) || - jsObject.IsDisposed) + !reference.TryGetTarget(out res) || + res.IsDisposed) { #pragma warning disable CS0612 // Type or member is obsolete - jsObject = mappedType switch + res = mappedType switch { JSHostImplementation.MappedType.JSObject => new JSObject(jsHandle), JSHostImplementation.MappedType.Array => new Array(jsHandle), @@ -58,21 +65,24 @@ namespace System.Runtime.InteropServices.JavaScript _ => throw new ArgumentOutOfRangeException(nameof(mappedType)) }; #pragma warning restore CS0612 // Type or member is obsolete - JSHostImplementation.s_csOwnedObjects[(int)jsHandle] = new WeakReference<JSObject>(jsObject, trackResurrection: true); + JSHostImplementation.s_csOwnedObjects[(int)jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true); } } if (shouldAddInflight != 0) { - jsObject.AddInFlight(); + res.AddInFlight(); } + jsObject = res; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result) { GCHandle h = (GCHandle)(IntPtr)gcHandle; result = h.Target!; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj) { return JSHostImplementation.GetJSOwnedObjectGCHandleRef(obj, GCHandleType.Normal); @@ -80,6 +90,7 @@ namespace System.Runtime.InteropServices.JavaScript // The JS layer invokes this method when the JS wrapper for a JS owned object // has been collected by the JS garbage collector + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void ReleaseJSOwnedObjectByGCHandle(IntPtr gcHandle) { GCHandle handle = (GCHandle)gcHandle; @@ -90,12 +101,14 @@ namespace System.Runtime.InteropServices.JavaScript } } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static IntPtr CreateTaskSource() { var tcs = new TaskCompletionSource<object>(); return GetJSOwnedObjectGCHandleRef(tcs); } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void SetTaskSourceResultRef(int tcsGCHandle, in object result) { GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle; @@ -104,6 +117,7 @@ namespace System.Runtime.InteropServices.JavaScript tcs.SetResult(result); } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void SetTaskSourceFailure(int tcsGCHandle, string reason) { GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle; @@ -112,6 +126,7 @@ namespace System.Runtime.InteropServices.JavaScript tcs.SetException(new JSException(reason)); } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void GetTaskSourceTaskRef(int tcsGCHandle, out object result) { GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle; @@ -120,11 +135,13 @@ namespace System.Runtime.InteropServices.JavaScript result = tcs.Task; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void TaskFromResultRef(in object obj, out object result) { result = Task.FromResult(obj); } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void SetupJSContinuationRef(in Task _task, JSObject continuationObj) { // HACK: Attempting to use the in-param will produce CS1628, so we make a temporary copy @@ -171,11 +188,13 @@ namespace System.Runtime.InteropServices.JavaScript } } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static string ObjectToStringRef(ref object o) { return o.ToString() ?? string.Empty; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static double GetDateValueRef(ref object dtv) { ArgumentNullException.ThrowIfNull(dtv); @@ -191,22 +210,27 @@ namespace System.Runtime.InteropServices.JavaScript // HACK: We need to implicitly box by using an 'object' out-param. // Note that the return value would have been boxed on the C#->JS transition anyway. + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void CreateDateTimeRef(double ticks, out object result) { DateTimeOffset unixTime = DateTimeOffset.FromUnixTimeMilliseconds((long)ticks); result = unixTime.DateTime; } + // TODO remove this to allow trimming of Uri assembly + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void CreateUriRef(string uri, out Uri result) { result = new Uri(uri); } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static bool IsSimpleArrayRef(ref object a) { return a is System.Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0; } + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static string GetCallSignatureRef(IntPtr _methodHandle, in object objForRuntimeType) { var methodHandle = JSHostImplementation.GetMethodHandleFromIntPtr(_methodHandle); @@ -231,13 +255,13 @@ namespace System.Runtime.InteropServices.JavaScript return new string(result); } - [MethodImplAttribute(MethodImplOptions.NoInlining)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void StopProfile() { } // Called by the AOT profiler to save profile data into INTERNAL.aot_profile_data - [MethodImplAttribute(MethodImplOptions.NoInlining)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static unsafe void DumpAotProfileData(ref byte buf, int len, string extraArg) { if (len == 0) @@ -248,12 +272,52 @@ namespace System.Runtime.InteropServices.JavaScript { var span = new ReadOnlySpan<byte>(p, len); // Send it to JS -#pragma warning disable CS0612 // Type or member is obsolete - var internalJS = (JSObject)JavaScriptImports.GetGlobalObject("INTERNAL"); - if (internalJS == null) throw new InvalidOperationException(); - internalJS.SetObjectProperty("aot_profile_data", Uint8Array.From(span)); -#pragma warning restore CS0612 // Type or member is obsolete + var module = JSHost.DotnetInstance.GetPropertyAsJSObject("INTERNAL"); + if (module == null) + throw new InvalidOperationException(); + + module.SetProperty("aot_profile_data", span.ToArray()); } } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + internal static JSObject CreateCSOwnedProxy(IntPtr jsHandle) + { + CreateCSOwnedProxyRef(jsHandle, JSHostImplementation.MappedType.JSObject, 0, out JSObject? res); + return res; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + public static IntPtr CreateTaskCallback() + { + JSHostImplementation.TaskCallback holder = new JSHostImplementation.TaskCallback(); + return GetJSOwnedObjectGCHandleRef(holder); + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + public static void CallDelegate(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; + GCHandle callback_gc_handle = (GCHandle)arg_return.slot.GCHandle; + + JSHostImplementation.ToManagedCallback? cb = (JSHostImplementation.ToManagedCallback?)callback_gc_handle.Target; + if (cb == null) + throw new InvalidOperationException("ToManagedCallback is null"); + + cb(arguments_buffer); + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + public static void CompleteTask(JSMarshalerArgument* arguments_buffer) + { + ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; + GCHandle callback_gc_handle = (GCHandle)arg_return.slot.GCHandle; + + JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)callback_gc_handle.Target; + if (holder == null || holder.Callback == null) + throw new InvalidOperationException("TaskCallback is null"); + + holder.Callback(arguments_buffer); + } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs new file mode 100644 index 00000000000..4a7b92ddb19 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace System.Runtime.InteropServices.JavaScript +{ + internal static unsafe partial class JavaScriptImports + { + [JSImport("INTERNAL.has_property")] + public static partial bool HasProperty(JSObject self, string propertyName); + [JSImport("INTERNAL.get_typeof_property")] + public static partial string GetTypeOfProperty(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial bool GetPropertyAsBoolean(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial int GetPropertyAsInt32(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial double GetPropertyAsDouble(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial string GetPropertyAsString(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial JSObject GetPropertyAsJSObject(JSObject self, string propertyName); + [JSImport("INTERNAL.get_property")] + public static partial byte[] GetPropertyAsByteArray(JSObject self, string propertyName); + + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyBool(JSObject self, string propertyName, bool value); + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyInt(JSObject self, string propertyName, int value); + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyDouble(JSObject self, string propertyName, double value); + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyString(JSObject self, string propertyName, string value); + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyJSObject(JSObject self, string propertyName, JSObject value); + [JSImport("INTERNAL.set_property")] + public static partial void SetPropertyBytes(JSObject self, string propertyName, byte[] value); + + [JSImport("INTERNAL.get_global_this")] + public static partial JSObject GetGlobalThis(); + [JSImport("INTERNAL.get_dotnet_instance")] + public static partial JSObject GetDotnetInstance(); + [JSImport("INTERNAL.dynamic_import")] + public static partial Task<JSObject> DynamicImport(string moduleName, string moduleUrl); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs index 76b4699f68c..e01dce221af 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.cs @@ -1,13 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.JavaScript { internal static unsafe partial class JavaScriptImports { + [MethodImplAttribute(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + public static void MarshalPromise(Span<JSMarshalerArgument> arguments) + { + fixed (JSMarshalerArgument* ptr = arguments) + { + Interop.Runtime.MarshalPromise(ptr); + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } + } + #region legacy + + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static string InvokeJS(string str) { string res = Interop.Runtime.InvokeJS(str, out int exception); @@ -16,6 +32,7 @@ namespace System.Runtime.InteropServices.JavaScript return res; } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static object GetGlobalObject(string? str = null) { int exception; @@ -28,6 +45,7 @@ namespace System.Runtime.InteropServices.JavaScript return jsObj; } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static IntPtr CreateCSOwnedObject(string typeName, object[] parms) { Interop.Runtime.CreateCSOwnedObjectRef(typeName, parms, out int exception, out object res); @@ -37,74 +55,6 @@ namespace System.Runtime.InteropServices.JavaScript return (IntPtr)(int)res; } - public static void CancelPromise(IntPtr promiseJSHandle) - { - Interop.Runtime.CancelPromiseRef(promiseJSHandle, out int exception, out string res); - if (exception != 0) - throw new JSException(res); - } - - public static Task<object> WebSocketOpen(string uri, object[]? subProtocols, Delegate onClosed, out JSObject webSocket, out IntPtr promiseJSHandle) - { - Interop.Runtime.WebSocketOpenRef(uri, subProtocols, onClosed, out IntPtr webSocketJSHandle, out promiseJSHandle, out int exception, out object res); - if (exception != 0) - throw new JSException((string)res); - webSocket = new JSObject((IntPtr)webSocketJSHandle); - - return (Task<object>)res; - } - - public static unsafe Task<object>? WebSocketSend(JSObject webSocket, ArraySegment<byte> buffer, int messageType, bool endOfMessage, out IntPtr promiseJSHandle) - { - fixed (byte* messagePtr = buffer.Array) - { - Interop.Runtime.WebSocketSend(webSocket.JSHandle, (IntPtr)messagePtr, buffer.Offset, buffer.Count, messageType, endOfMessage, out promiseJSHandle, out int exception, out object res); - if (exception != 0) - throw new JSException((string)res); - - if (res == null) - { - return null; - } - - return (Task<object>)res; - } - } - - public static unsafe Task<object>? WebSocketReceive(JSObject webSocket, ArraySegment<byte> buffer, ReadOnlySpan<int> response, out IntPtr promiseJSHandle) - { - fixed (int* responsePtr = response) - fixed (byte* bufferPtr = buffer.Array) - { - Interop.Runtime.WebSocketReceive(webSocket.JSHandle, (IntPtr)bufferPtr, buffer.Offset, buffer.Count, (IntPtr)responsePtr, out promiseJSHandle, out int exception, out object res); - if (exception != 0) - throw new JSException((string)res); - if (res == null) - { - return null; - } - return (Task<object>)res; - } - } - - public static Task<object>? WebSocketClose(JSObject webSocket, int code, string? reason, bool waitForCloseReceived, out IntPtr promiseJSHandle) - { - Interop.Runtime.WebSocketCloseRef(webSocket.JSHandle, code, reason, waitForCloseReceived, out promiseJSHandle, out int exception, out object res); - if (exception != 0) - throw new JSException((string)res); - - if (res == null) - { - return null; - } - return (Task<object>)res; - } - - public static void WebSocketAbort(JSObject webSocket) - { - Interop.Runtime.WebSocketAbort(webSocket.JSHandle, out int exception, out string res); - if (exception != 0) - throw new JSException(res); - } + #endregion } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs index e3ca5f7bc31..de5715f99cd 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs @@ -11,12 +11,20 @@ namespace System.Runtime.InteropServices.JavaScript [SupportedOSPlatform("browser")] // @kg: Do we really need to platform-lock JSException? public sealed class JSException : Exception { + internal JSObject? jsException; + /// <summary> /// Initializes a new instance of the JSException class with a specified error message. /// </summary> /// <param name="msg">The message that describes the error.</param> public JSException(string msg) : base(msg) { + jsException = null; + } + + internal JSException(string msg, JSObject? jsException) : base(msg) + { + this.jsException = jsException; } /// <inheritdoc /> @@ -24,20 +32,47 @@ namespace System.Runtime.InteropServices.JavaScript { get { - return null; + var bs = base.StackTrace; + if (jsException == null) + { + return bs; + } + string? jsStackTrace = jsException.GetPropertyAsString("stack"); + if (jsStackTrace == null) + { + if (bs == null) + { + return null; + } + } + else if (jsStackTrace.StartsWith(Message + "\n")) + { + // Some JS runtimes insert the error message at the top of the stack, some don't, + // so normalize it by using the stack as the result if it already contains the error + jsStackTrace = jsStackTrace.Substring(Message.Length + 1); + } + + if (bs == null) + { + return jsStackTrace; + } + return base.StackTrace + "\r\n" + jsStackTrace; } + } /// <inheritdoc /> public override bool Equals(object? obj) { - return base.Equals(obj); + return obj is JSException other && other.jsException == jsException; } /// <inheritdoc /> public override int GetHashCode() { - return base.GetHashCode(); + return jsException == null + ? base.GetHashCode() + : base.GetHashCode() * jsException.GetHashCode(); } /// <inheritdoc /> diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index b82756736b9..f73515faaea 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -18,12 +18,9 @@ namespace System.Runtime.InteropServices.JavaScript public sealed partial class JSFunctionBinding { #region intentionaly opaque internal structure - -#pragma warning disable CS0649 // temporary until we have implementation internal unsafe JSBindingHeader* Header; internal unsafe JSBindingType* Sigs;// points to first arg, not exception, not result internal JSObject? JSFunction; -#pragma warning restore CS0649 [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct JSBindingHeader @@ -49,6 +46,72 @@ namespace System.Runtime.InteropServices.JavaScript internal MarshalerType Arg3MarshalerType; } + internal unsafe int ArgumentCount + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Header[0].ArgumentCount; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + Header[0].ArgumentCount = value; + } + } + + internal unsafe int Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Header[0].Version; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + Header[0].Version = value; + } + } + + internal unsafe JSBindingType Result + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Header[0].Result; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + Header[0].Result = value; + } + } + + internal unsafe JSBindingType Exception + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Header[0].Exception; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + Header[0].Exception = value; + } + } + + // one based position of args, not exception, not result + internal unsafe JSBindingType this[int position] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Sigs[position - 1]; + } + } + #endregion /// <summary> @@ -58,18 +121,21 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InvokeJS(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments) { - throw new NotImplementedException(); + InvokeJSImpl(signature.JSFunction!, arguments); } /// <summary> /// Locates and binds a JavaScript function given name and module so that it can later be invoked by managed callers. /// This API supports JSImport infrastructure and is not intended to be used directly from your code. /// </summary> - // JavaScriptExports need to be protected from trimming because they are used from C/JS code which linker can't see + // JavaScriptExports need to be protected from trimming because they are used from C/JS code which IL linker can't see [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JavaScriptExports", "System.Runtime.InteropServices.JavaScript")] public static JSFunctionBinding BindJSFunction(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) { - throw new NotImplementedException(); + if (RuntimeInformation.OSArchitecture != Architecture.Wasm) + throw new PlatformNotSupportedException(); + + return BindJSFunctionImpl(functionName, moduleName, signatures); } /// <summary> @@ -78,7 +144,52 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public static JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures) { - throw new NotImplementedException(); + if (RuntimeInformation.OSArchitecture != Architecture.Wasm) + throw new PlatformNotSupportedException(); + + return BindManagedFunctionImpl(fullyQualifiedName, signatureHash, signatures); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span<JSMarshalerArgument> arguments) + { + IntPtr functionJSHandle = jsFunction.JSHandle; + fixed (JSMarshalerArgument* ptr = arguments) + { + Interop.Runtime.InvokeJSFunction(functionJSHandle, ptr); + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures) + { + var signature = JSHostImplementation.GetMethodSignature(signatures); + + Interop.Runtime.BindJSFunction(functionName, moduleName, signature.Header, out IntPtr jsFunctionHandle, out int isException, out object exceptionMessage); + if (isException != 0) + throw new JSException((string)exceptionMessage); + + signature.JSFunction = JavaScriptExports.CreateCSOwnedProxy(jsFunctionHandle); + + return signature; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQualifiedName, int signatureHash, ReadOnlySpan<JSMarshalerType> signatures) + { + var signature = JSHostImplementation.GetMethodSignature(signatures); + + Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage); + if (isException != 0) + { + throw new JSException((string)exceptionMessage); + } + return signature; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs index 0ea4fa39315..b549ceb7f85 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHost.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Runtime.Versioning; using System.Threading; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.JavaScript { @@ -20,7 +21,7 @@ namespace System.Runtime.InteropServices.JavaScript { get { - throw new NotImplementedException(); + return JavaScriptImports.GetGlobalThis(); } } @@ -31,7 +32,7 @@ namespace System.Runtime.InteropServices.JavaScript { get { - throw new NotImplementedException(); + return JavaScriptImports.GetDotnetInstance(); } } @@ -43,9 +44,10 @@ namespace System.Runtime.InteropServices.JavaScript /// <param name="moduleUrl">The location of the module file.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A proxy for the JavaScript object that contains the module's exports.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken); } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs index a8a1ea610c5..3009089133e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Types.cs @@ -5,6 +5,13 @@ namespace System.Runtime.InteropServices.JavaScript { internal static partial class JSHostImplementation { + internal unsafe delegate void ToManagedCallback(JSMarshalerArgument* arguments_buffer); + + public sealed class TaskCallback + { + public ToManagedCallback? Callback; + } + [StructLayout(LayoutKind.Explicit)] public struct IntPtrAndHandle { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index 3e2820b0e00..aa1ad66a34c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -5,10 +5,12 @@ using System.Threading.Tasks; using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; namespace System.Runtime.InteropServices.JavaScript { - internal static unsafe partial class JSHostImplementation + internal static partial class JSHostImplementation { private const string TaskGetResultName = "get_Result"; private static readonly MethodInfo s_taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!; @@ -17,6 +19,7 @@ namespace System.Runtime.InteropServices.JavaScript // we use this to maintain identity of GCHandle for a managed object public static Dictionary<object, IntPtr> s_gcHandleFromJSOwnedObject = new Dictionary<object, IntPtr>(ReferenceEqualityComparer.Instance); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RegisterCSOwnedObject(JSObject proxy) { lock (s_csOwnedObjects) @@ -25,6 +28,7 @@ namespace System.Runtime.InteropServices.JavaScript } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReleaseCSOwnedObject(IntPtr jsHandle) { if (jsHandle != IntPtr.Zero) @@ -37,6 +41,7 @@ namespace System.Runtime.InteropServices.JavaScript } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object? GetTaskResult(Task task) { MethodInfo method = GetTaskResultMethodInfo(task.GetType()); @@ -47,6 +52,7 @@ namespace System.Runtime.InteropServices.JavaScript throw new InvalidOperationException(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReleaseInFlight(object obj) { JSObject? jsObj = obj as JSObject; @@ -78,6 +84,7 @@ namespace System.Runtime.InteropServices.JavaScript } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) { var temp = new IntPtrAndHandle { ptr = ptr }; @@ -247,5 +254,66 @@ namespace System.Runtime.InteropServices.JavaScript throw new InvalidOperationException(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ThrowException(ref JSMarshalerArgument arg) + { + arg.ToManaged(out Exception? ex); + + if (ex != null) + { + throw ex; + } + throw new InvalidProgramException(); + } + + public static async Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken ) + { + Task<JSObject> modulePromise = JavaScriptImports.DynamicImport(moduleName, moduleUrl); + var wrappedTask = CancelationHelper(modulePromise, cancellationToken); + await Task.Yield();// this helps to finish the import before we bind the module in [JSImport] + return await wrappedTask.ConfigureAwait(true); + } + + private static async Task<JSObject> CancelationHelper(Task<JSObject> jsTask, CancellationToken cancellationToken) + { + if (jsTask.IsCompletedSuccessfully) + { + return jsTask.Result; + } + using (var receiveRegistration = cancellationToken.Register(() => + { + CancelablePromise.CancelPromise(jsTask); + })) + { + return await jsTask.ConfigureAwait(true); + } + } + + // res type is first argument + internal static unsafe JSFunctionBinding GetMethodSignature(ReadOnlySpan<JSMarshalerType> types) + { + int argsCount = types.Length - 1; + int size = JSFunctionBinding.JSBindingHeader.JSMarshalerSignatureHeaderSize + ((argsCount + 2) * sizeof(JSFunctionBinding.JSBindingType)); + // this is never unallocated + IntPtr buffer = Marshal.AllocHGlobal(size); + + var signature = new JSFunctionBinding + { + Header = (JSFunctionBinding.JSBindingHeader*)buffer, + Sigs = (JSFunctionBinding.JSBindingType*)(buffer + JSFunctionBinding.JSBindingHeader.JSMarshalerSignatureHeaderSize + (2 * sizeof(JSFunctionBinding.JSBindingType))), + }; + + signature.Version = 1; + signature.ArgumentCount = argsCount; + signature.Exception = JSMarshalerType.Exception._signatureType; + signature.Result = types[0]._signatureType; + for (int i = 0; i < argsCount; i++) + { + signature.Sigs[i] = types[i + 1]._signatureType; + } + + return signature; + } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs index d370f18e32b..268c41385e2 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSMarshalerArgument.cs @@ -16,13 +16,45 @@ namespace System.Runtime.InteropServices.JavaScript [EditorBrowsable(EditorBrowsableState.Never)] public partial struct JSMarshalerArgument { -#pragma warning disable CS0649 // temporary until we have implementation internal JSMarshalerArgumentImpl slot; -#pragma warning restore CS0649 [StructLayout(LayoutKind.Explicit, Pack = 16, Size = 16)] internal struct JSMarshalerArgumentImpl { + [FieldOffset(0)] + internal bool BooleanValue; + [FieldOffset(0)] + internal byte ByteValue; + [FieldOffset(0)] + internal char CharValue; + [FieldOffset(0)] + internal short Int16Value; + [FieldOffset(0)] + internal int Int32Value; + [FieldOffset(0)] + internal long Int64Value;// must be alligned to 8 because of HEAPI64 alignment + [FieldOffset(0)] + internal float SingleValue; + [FieldOffset(0)] + internal double DoubleValue;// must be alligned to 8 because of Module.HEAPF64 view alignment + [FieldOffset(0)] + internal IntPtr IntPtrValue; + + [FieldOffset(4)] + internal IntPtr JSHandle; + [FieldOffset(4)] + internal IntPtr GCHandle; + [FieldOffset(4)] + internal MarshalerType ElementType; + + [FieldOffset(8)] + internal int Length; + + /// <summary> + /// Discriminator + /// </summary> + [FieldOffset(12)] + internal MarshalerType Type; } /// <summary> @@ -31,7 +63,7 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Initialize() { - throw new NotImplementedException(); + slot.Type = MarshalerType.None; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs index 4b7f75536a1..551379f5569 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs @@ -24,7 +24,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasProperty(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.HasProperty(this, propertyName); } /// <summary> @@ -33,7 +34,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetTypeOfProperty(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetTypeOfProperty(this, propertyName); } /// <summary> @@ -45,7 +47,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GetPropertyAsBoolean(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsBoolean(this, propertyName); } /// <summary> @@ -57,7 +60,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetPropertyAsInt32(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsInt32(this, propertyName); } /// <summary> @@ -69,7 +73,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public double GetPropertyAsDouble(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsDouble(this, propertyName); } /// <summary> @@ -81,7 +86,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public string? GetPropertyAsString(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsString(this, propertyName); } /// <summary> @@ -93,7 +99,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public JSObject? GetPropertyAsJSObject(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsJSObject(this, propertyName); } /// <summary> @@ -106,7 +113,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[]? GetPropertyAsByteArray(string propertyName) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + return JavaScriptImports.GetPropertyAsByteArray(this, propertyName); } /// <summary> @@ -115,7 +123,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, bool value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyBool(this, propertyName, value); } /// <summary> @@ -124,7 +133,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, int value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyInt(this, propertyName, value); } /// <summary> @@ -133,7 +143,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, double value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyDouble(this, propertyName, value); } /// <summary> @@ -142,7 +153,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, string? value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyString(this, propertyName, value); } /// <summary> @@ -151,7 +163,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, JSObject? value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyJSObject(this, propertyName, value); } /// <summary> @@ -161,7 +174,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetProperty(string propertyName, byte[]? value) { - throw new NotImplementedException(); + ObjectDisposedException.ThrowIf(IsDisposed, this); + JavaScriptImports.SetPropertyBytes(this, propertyName, value); } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Array.cs index 180cc86e644..2c669c0673e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Array.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Array.cs @@ -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. +using System.Runtime.CompilerServices; + namespace System.Runtime.InteropServices.JavaScript { /// <summary> @@ -81,6 +83,7 @@ namespace System.Runtime.InteropServices.JavaScript /// <param name="i">The index.</param> public object this[int i] { + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 get { this.AssertNotDisposed(); @@ -92,6 +95,7 @@ namespace System.Runtime.InteropServices.JavaScript JSHostImplementation.ReleaseInFlight(indexValue); return indexValue; } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 set { this.AssertNotDisposed(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs index 6559d7798d6..abafd63e932 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Runtime.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.JavaScript { @@ -19,24 +19,6 @@ namespace System.Runtime.InteropServices.JavaScript public static object GetGlobalObject(string str) => JavaScriptImports.GetGlobalObject(str); - public static void CancelPromise(IntPtr promiseJSHandle) - => JavaScriptImports.CancelPromise(promiseJSHandle); - - public static Task<object> WebSocketOpen(string uri, object[]? subProtocols, Delegate onClosed, out JSObject webSocket, out IntPtr promiseJSHandle) - => JavaScriptImports.WebSocketOpen(uri, subProtocols, onClosed, out webSocket, out promiseJSHandle); - - public static unsafe Task<object>? WebSocketSend(JSObject webSocket, ArraySegment<byte> buffer, int messageType, bool endOfMessage, out IntPtr promiseJSHandle) - => JavaScriptImports.WebSocketSend(webSocket, buffer, messageType, endOfMessage, out promiseJSHandle); - - public static unsafe Task<object>? WebSocketReceive(JSObject webSocket, ArraySegment<byte> buffer, ReadOnlySpan<int> response, out IntPtr promiseJSHandle) - => JavaScriptImports.WebSocketReceive(webSocket, buffer, response, out promiseJSHandle); - - public static Task<object>? WebSocketClose(JSObject webSocket, int code, string? reason, bool waitForCloseReceived, out IntPtr promiseJSHandle) - => JavaScriptImports.WebSocketClose(webSocket, code, reason, waitForCloseReceived, out promiseJSHandle); - - public static void WebSocketAbort(JSObject webSocket) - => JavaScriptImports.WebSocketAbort(webSocket); - /// <summary> /// Invoke a named method of the object, or throws a JSException on error. /// </summary> @@ -57,6 +39,7 @@ namespace System.Runtime.InteropServices.JavaScript /// valuews. /// </para> /// </returns> + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static object Invoke(this JSObject self, string method, params object?[] args) { ArgumentNullException.ThrowIfNull(self); @@ -91,6 +74,7 @@ namespace System.Runtime.InteropServices.JavaScript /// valuews. /// </para> /// </returns> + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static object GetObjectProperty(this JSObject self, string name) { ArgumentNullException.ThrowIfNull(self); @@ -115,6 +99,7 @@ namespace System.Runtime.InteropServices.JavaScript /// float[], double[]) </param> /// <param name="createIfNotExists">Defaults to <see langword="true"/> and creates the property on the javascript object if not found, if set to <see langword="false"/> it will not create the property if it does not exist. If the property exists, the value is updated with the provided value.</param> /// <param name="hasOwnProperty"></param> + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void SetObjectProperty(this JSObject self, string name, object? value, bool createIfNotExists = true, bool hasOwnProperty = false) { ArgumentNullException.ThrowIfNull(self); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Uint8Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Uint8Array.cs index e829155a550..6bea6d54d5d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Uint8Array.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Legacy/Uint8Array.cs @@ -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. +using System.Runtime.CompilerServices; + namespace System.Runtime.InteropServices.JavaScript { [Obsolete] @@ -34,6 +36,7 @@ namespace System.Runtime.InteropServices.JavaScript public static implicit operator Uint8Array(Span<byte> span) => From(span); + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public byte[] ToArray() { this.AssertNotDisposed(); @@ -45,6 +48,7 @@ namespace System.Runtime.InteropServices.JavaScript return (byte[])res; } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static unsafe Uint8Array From(ReadOnlySpan<byte> span) { // source has to be instantiated. diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs index 0674cdc3ba0..4e245866c0e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/MarshalerType.cs @@ -35,8 +35,6 @@ namespace System.Runtime.InteropServices.JavaScript Action, Function, - // FUTURE NativeMarshalling, - #if !JSIMPORTGENERATOR // only on runtime JSException, diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.BigInt64.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.BigInt64.cs index d9a232835b6..f53960e618e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.BigInt64.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.BigInt64.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManagedBig(out long value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.Int64Value; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJSBig(long value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.BigInt64; + slot.Int64Value = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManagedBig(out long? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.Int64Value; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJSBig(long? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.BigInt64; + slot.Int64Value = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Bool.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Bool.cs index eec69ac0e82..336db0c50d2 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Bool.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Bool.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out bool value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.BooleanValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(bool value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Boolean; + slot.BooleanValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out bool? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.BooleanValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(bool? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Boolean; + slot.BooleanValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Byte.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Byte.cs index 706348e26df..218b40336f6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Byte.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Byte.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out byte value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.ByteValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(byte value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Byte; + slot.ByteValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out byte? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.ByteValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(byte? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Byte; + slot.ByteValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } /// <summary> @@ -53,7 +72,14 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToManaged(out byte[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = new byte[slot.Length]; + Marshal.Copy(slot.IntPtrValue, value, 0, slot.Length); + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -62,7 +88,16 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(byte[]? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Length = value.Length; + slot.Type = MarshalerType.Array; + slot.IntPtrValue = Marshal.AllocHGlobal(value.Length * sizeof(byte)); + slot.ElementType = MarshalerType.Byte; + Marshal.Copy(value, 0, slot.IntPtrValue, slot.Length); } /// <summary> @@ -72,7 +107,10 @@ namespace System.Runtime.InteropServices.JavaScript // this only supports array round-trip, there is no way how to create ArraySegment in JS public unsafe void ToManaged(out ArraySegment<byte> value) { - throw new NotImplementedException(); + var array = (byte[])((GCHandle)slot.GCHandle).Target!; + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)); + int byteOffset = (int)(slot.IntPtrValue - (nint)refPtr); + value = new ArraySegment<byte>(array, byteOffset, slot.Length); } /// <summary> @@ -81,7 +119,16 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(ArraySegment<byte> value) { - throw new NotImplementedException(); + if (value.Array == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.ArraySegment; + slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandleRef(value.Array, GCHandleType.Pinned); + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); + slot.IntPtrValue = refPtr + value.Offset; + slot.Length = value.Count; } /// <summary> @@ -90,7 +137,7 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToManaged(out Span<byte> value) { - throw new NotImplementedException(); + value = new Span<byte>((void*)slot.IntPtrValue, slot.Length); } /// <summary> @@ -100,7 +147,9 @@ namespace System.Runtime.InteropServices.JavaScript /// <remarks>caller is responsible for pinning</remarks> public unsafe void ToJS(Span<byte> value) { - throw new NotImplementedException(); + slot.Length = value.Length; + slot.IntPtrValue = (IntPtr)Unsafe.AsPointer(ref value.GetPinnableReference()); + slot.Type = MarshalerType.Span; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Char.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Char.cs index b56a4135871..8a0500068e2 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Char.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Char.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out char value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.CharValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(char value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Char; + slot.CharValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out char? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.CharValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(char? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Char; + slot.CharValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.DateTime.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.DateTime.cs index 1d963a92bc4..3984941ab52 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.DateTime.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.DateTime.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out DateTimeOffset value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = DateTimeOffset.FromUnixTimeMilliseconds((long)slot.DoubleValue); } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(DateTimeOffset value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.DateTimeOffset; + slot.DoubleValue = (double)value.ToUnixTimeMilliseconds(); } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out DateTimeOffset? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = DateTimeOffset.FromUnixTimeMilliseconds((long)slot.DoubleValue); } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(DateTimeOffset? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.DateTimeOffset; + slot.DoubleValue = value.Value.ToUnixTimeMilliseconds(); ; + } + else + { + slot.Type = MarshalerType.None; + } } /// <summary> @@ -54,7 +73,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out DateTime value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = DateTimeOffset.FromUnixTimeMilliseconds((long)slot.DoubleValue).UtcDateTime; } /// <summary> @@ -64,7 +88,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(DateTime value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.DateTime; + slot.DoubleValue = new DateTimeOffset(value).ToUnixTimeMilliseconds(); } /// <summary> @@ -74,7 +99,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out DateTime? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = DateTimeOffset.FromUnixTimeMilliseconds((long)slot.DoubleValue).UtcDateTime; } /// <summary> @@ -84,7 +114,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(DateTime? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.DateTime; + slot.DoubleValue = new DateTimeOffset(value.Value).ToUnixTimeMilliseconds(); + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Double.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Double.cs index 88dabbed021..2dcb1d445d5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Double.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Double.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out double value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.DoubleValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(double value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Double; + slot.DoubleValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out double? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.DoubleValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(double? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Double; + slot.DoubleValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } /// <summary> @@ -54,7 +73,14 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out double[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = new double[slot.Length]; + Marshal.Copy(slot.IntPtrValue, value, 0, slot.Length); + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -64,7 +90,16 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(double[] value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.Array; + slot.IntPtrValue = Marshal.AllocHGlobal(value.Length * sizeof(double)); + slot.Length = value.Length; + slot.ElementType = MarshalerType.Double; + Marshal.Copy(value, 0, slot.IntPtrValue, slot.Length); } /// <summary> @@ -74,7 +109,10 @@ namespace System.Runtime.InteropServices.JavaScript // this only supports array round-trip public unsafe void ToManaged(out ArraySegment<double> value) { - throw new NotImplementedException(); + var array = (double[])((GCHandle)slot.GCHandle).Target!; + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)); + int byteOffset = (int)(slot.IntPtrValue - (nint)refPtr); + value = new ArraySegment<double>(array, byteOffset / sizeof(double), slot.Length); } /// <summary> @@ -83,7 +121,16 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(ArraySegment<double> value) { - throw new NotImplementedException(); + if (value.Array == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.ArraySegment; + slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandleRef(value.Array, GCHandleType.Pinned); + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); + slot.IntPtrValue = refPtr + (value.Offset * sizeof(double)); + slot.Length = value.Count; } /// <summary> @@ -92,7 +139,7 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToManaged(out Span<double> value) { - throw new NotImplementedException(); + value = new Span<double>((void*)slot.IntPtrValue, slot.Length); } /// <summary> @@ -102,7 +149,9 @@ namespace System.Runtime.InteropServices.JavaScript /// <remarks>caller is responsible for pinning</remarks> public unsafe void ToJS(Span<double> value) { - throw new NotImplementedException(); + slot.Length = value.Length; + slot.IntPtrValue = (IntPtr)Unsafe.AsPointer(ref value.GetPinnableReference()); + slot.Type = MarshalerType.Span; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs index ac5eb413645..674b5899ceb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Exception.cs @@ -14,7 +14,31 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out Exception? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + if (slot.Type == MarshalerType.Exception) + { + // this is managed exception round-trip +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + value = (Exception)((GCHandle)slot.GCHandle).Target; +#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. + return; + } + + JSObject? jsException = null; + if (slot.JSHandle != IntPtr.Zero) + { + // this is JSException round-trip + jsException = JavaScriptExports.CreateCSOwnedProxy(slot.JSHandle); + } + + string? message; + ToManaged(out message); + + value = new JSException(message!, jsException); } /// <summary> @@ -24,7 +48,36 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(Exception? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + } + else + { + Exception cpy = value; + if (cpy is AggregateException ae && ae.InnerExceptions.Count == 1) + { + cpy = ae.InnerExceptions[0]; + } + + var jse = cpy as JSException; + if (jse != null && jse.jsException != null) + { + // this is JSException roundtrip + if (jse.jsException.IsDisposed) + { + throw new ObjectDisposedException(nameof(value)); + } + slot.Type = MarshalerType.JSException; + slot.JSHandle = jse.jsException.JSHandle; + } + else + { + ToJS(cpy.Message); + slot.Type = MarshalerType.Exception; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cpy); + } + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs index 9bcc9fd4eb3..b4cd56423b9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Func.cs @@ -5,76 +5,375 @@ namespace System.Runtime.InteropServices.JavaScript { public partial struct JSMarshalerArgument { + private sealed class ActionJS + { + private JSObject JSObject; + + public ActionJS(IntPtr jsHandle) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + } + + public void InvokeJS() + { + // JSObject (held by this lambda) would be collected by GC after the lambda is collected + // and would also allow the JS function to be collected + + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + args_exception.Initialize(); + args_return.Initialize(); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + } + + } + + private sealed class ActionJS<T> + { + private ArgumentToJSCallback<T> Arg1Marshaler; + private JSObject JSObject; + + public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + } + + public void InvokeJS(T arg1) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + } + } + + private sealed class ActionJS<T1, T2> + { + private ArgumentToJSCallback<T1> Arg1Marshaler; + private ArgumentToJSCallback<T2> Arg2Marshaler; + private JSObject JSObject; + + public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + Arg2Marshaler = arg2Marshaler; + } + + public void InvokeJS(T1 arg1, T2 arg2) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + ref JSMarshalerArgument args_arg2 = ref arguments[3]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + Arg2Marshaler(ref args_arg2, arg2); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + } + } + + private sealed class ActionJS<T1, T2, T3> + { + private ArgumentToJSCallback<T1> Arg1Marshaler; + private ArgumentToJSCallback<T2> Arg2Marshaler; + private ArgumentToJSCallback<T3> Arg3Marshaler; + private JSObject JSObject; + + public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + Arg2Marshaler = arg2Marshaler; + Arg3Marshaler = arg3Marshaler; + } + + public void InvokeJS(T1 arg1, T2 arg2, T3 arg3) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[5]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + ref JSMarshalerArgument args_arg2 = ref arguments[3]; + ref JSMarshalerArgument args_arg3 = ref arguments[4]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + Arg2Marshaler(ref args_arg2, arg2); + Arg3Marshaler(ref args_arg3, arg3); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + } + } + /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged(out Action value) + public unsafe void ToManaged(out Action? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new ActionJS(slot.JSHandle).InvokeJS; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T>(out Action<T> value, ArgumentToJSCallback<T> arg1Marshaler) + public unsafe void ToManaged<T>(out Action<T>? value, ArgumentToJSCallback<T> arg1Marshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new ActionJS<T>(slot.JSHandle, arg1Marshaler).InvokeJS; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T1, T2>(out Action<T1, T2> value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler) + public unsafe void ToManaged<T1, T2>(out Action<T1, T2>? value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new ActionJS<T1, T2>(slot.JSHandle, arg1Marshaler, arg2Marshaler).InvokeJS; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T1, T2, T3>(out Action<T1, T2, T3> value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler) + public unsafe void ToManaged<T1, T2, T3>(out Action<T1, T2, T3>? value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new ActionJS<T1, T2, T3>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler).InvokeJS; + } + + private sealed class FuncJS<TResult> + { + private JSObject JSObject; + private ArgumentToManagedCallback<TResult> ResMarshaler; + + public FuncJS(IntPtr jsHandle, ArgumentToManagedCallback<TResult> resMarshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + ResMarshaler = resMarshaler; + } + + public TResult InvokeJS() + { + // JSObject (held by this lambda) would be collected by GC after the lambda is collected + // and would also allow the JS function to be collected + + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + args_exception.Initialize(); + args_return.Initialize(); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + + ResMarshaler(ref args_return, out TResult res); + return res; + } + + } + + private sealed class FuncJS<T, TResult> + { + private ArgumentToJSCallback<T> Arg1Marshaler; + private ArgumentToManagedCallback<TResult> ResMarshaler; + private JSObject JSObject; + + public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + ResMarshaler = resMarshaler; + } + + public TResult InvokeJS(T arg1) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + + ResMarshaler(ref args_return, out TResult res); + return res; + } + } + + private sealed class FuncJS<T1, T2, TResult> + { + private ArgumentToJSCallback<T1> Arg1Marshaler; + private ArgumentToJSCallback<T2> Arg2Marshaler; + private ArgumentToManagedCallback<TResult> ResMarshaler; + private JSObject JSObject; + + public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + Arg2Marshaler = arg2Marshaler; + ResMarshaler = resMarshaler; + } + + public TResult InvokeJS(T1 arg1, T2 arg2) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + ref JSMarshalerArgument args_arg2 = ref arguments[3]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + Arg2Marshaler(ref args_arg2, arg2); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + + ResMarshaler(ref args_return, out TResult res); + return res; + } + } + + private sealed class FuncJS<T1, T2, T3, TResult> + { + private ArgumentToJSCallback<T1> Arg1Marshaler; + private ArgumentToJSCallback<T2> Arg2Marshaler; + private ArgumentToJSCallback<T3> Arg3Marshaler; + private ArgumentToManagedCallback<TResult> ResMarshaler; + private JSObject JSObject; + + public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + { + JSObject = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + Arg1Marshaler = arg1Marshaler; + Arg2Marshaler = arg2Marshaler; + Arg3Marshaler = arg3Marshaler; + ResMarshaler = resMarshaler; + } + + public TResult InvokeJS(T1 arg1, T2 arg2, T3 arg3) + { + Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[5]; + ref JSMarshalerArgument args_exception = ref arguments[0]; + ref JSMarshalerArgument args_return = ref arguments[1]; + ref JSMarshalerArgument args_arg1 = ref arguments[2]; + ref JSMarshalerArgument args_arg2 = ref arguments[3]; + ref JSMarshalerArgument args_arg3 = ref arguments[4]; + + args_exception.Initialize(); + args_return.Initialize(); + Arg1Marshaler(ref args_arg1, arg1); + Arg2Marshaler(ref args_arg2, arg2); + Arg3Marshaler(ref args_arg3, arg3); + + JSFunctionBinding.InvokeJSImpl(JSObject, arguments); + + ResMarshaler(ref args_return, out TResult res); + return res; + } } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<TResult>(out Func<TResult> value, ArgumentToManagedCallback<TResult> resMarshaler) + public unsafe void ToManaged<TResult>(out Func<TResult>? value, ArgumentToManagedCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new FuncJS<TResult>(slot.JSHandle, resMarshaler).InvokeJS; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T, TResult>(out Func<T, TResult> value, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + public unsafe void ToManaged<T, TResult>(out Func<T, TResult>? value, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new FuncJS<T, TResult>(slot.JSHandle, arg1Marshaler, resMarshaler).InvokeJS; + } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T1, T2, TResult>(out Func<T1, T2, TResult> value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + public unsafe void ToManaged<T1, T2, TResult>(out Func<T1, T2, TResult>? value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new FuncJS<T1, T2, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, resMarshaler).InvokeJS; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T1, T2, T3, TResult>(out Func<T1, T2, T3, TResult> value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) + public unsafe void ToManaged<T1, T2, T3, TResult>(out Func<T1, T2, T3, TResult>? value, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new FuncJS<T1, T2, T3, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler, resMarshaler).InvokeJS; } /// <summary> @@ -83,7 +382,22 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(Action value) { - throw new NotImplementedException(); + Action cpy = value; + // TODO: we could try to cache value -> exising GCHandle + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + try + { + cpy.Invoke(); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Function; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -92,7 +406,23 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T>(Action<T> value, ArgumentToManagedCallback<T> arg1Marshaler) { - throw new NotImplementedException(); + Action<T> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + try + { + arg1Marshaler(ref arg1, out T arg1cs); + cpy.Invoke(arg1cs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Action; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -101,7 +431,25 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T1, T2>(Action<T1, T2> value, ArgumentToManagedCallback<T1> arg1Marshaler, ArgumentToManagedCallback<T2> arg2Marshaler) { - throw new NotImplementedException(); + Action<T1, T2> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + ref JSMarshalerArgument arg2 = ref arguments[3]; + try + { + arg1Marshaler(ref arg1, out T1 arg1cs); + arg2Marshaler(ref arg2, out T2 arg2cs); + cpy.Invoke(arg1cs, arg2cs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Action; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -110,7 +458,27 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T1, T2, T3>(Action<T1, T2, T3> value, ArgumentToManagedCallback<T1> arg1Marshaler, ArgumentToManagedCallback<T2> arg2Marshaler, ArgumentToManagedCallback<T3> arg3Marshaler) { - throw new NotImplementedException(); + Action<T1, T2, T3> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + ref JSMarshalerArgument arg2 = ref arguments[3]; + ref JSMarshalerArgument arg3 = ref arguments[4]; + try + { + arg1Marshaler(ref arg1, out T1 arg1cs); + arg2Marshaler(ref arg2, out T2 arg2cs); + arg3Marshaler(ref arg3, out T3 arg3cs); + cpy.Invoke(arg1cs, arg2cs, arg3cs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Action; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -119,7 +487,23 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<TResult>(Func<TResult> value, ArgumentToJSCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + Func<TResult> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument res = ref arguments[1]; + try + { + TResult resCs = cpy.Invoke(); + resMarshaler(ref res, resCs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Function; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -128,7 +512,25 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T, TResult>(Func<T, TResult> value, ArgumentToManagedCallback<T> arg1Marshaler, ArgumentToJSCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + Func<T, TResult> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument res = ref arguments[1]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + try + { + arg1Marshaler(ref arg1, out T arg1cs); + TResult resCs = cpy.Invoke(arg1cs); + resMarshaler(ref res, resCs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + slot.Type = MarshalerType.Function; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -137,7 +539,29 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T1, T2, TResult>(Func<T1, T2, TResult> value, ArgumentToManagedCallback<T1> arg1Marshaler, ArgumentToManagedCallback<T2> arg2Marshaler, ArgumentToJSCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + Func<T1, T2, TResult> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument res = ref arguments[1]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + ref JSMarshalerArgument arg2 = ref arguments[3]; + try + { + arg1Marshaler(ref arg1, out T1 arg1cs); + arg2Marshaler(ref arg2, out T2 arg2cs); + TResult resCs = cpy.Invoke(arg1cs, arg2cs); + resMarshaler(ref res, resCs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + + + slot.Type = MarshalerType.Function; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } /// <summary> @@ -146,7 +570,31 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> value, ArgumentToManagedCallback<T1> arg1Marshaler, ArgumentToManagedCallback<T2> arg2Marshaler, ArgumentToManagedCallback<T3> arg3Marshaler, ArgumentToJSCallback<TResult> resMarshaler) { - throw new NotImplementedException(); + Func<T1, T2, T3, TResult> cpy = value; + JSHostImplementation.ToManagedCallback cb = (JSMarshalerArgument* arguments) => + { + ref JSMarshalerArgument exc = ref arguments[0]; + ref JSMarshalerArgument res = ref arguments[1]; + ref JSMarshalerArgument arg1 = ref arguments[2]; + ref JSMarshalerArgument arg2 = ref arguments[3]; + ref JSMarshalerArgument arg3 = ref arguments[4]; + try + { + arg1Marshaler(ref arg1, out T1 arg1cs); + arg2Marshaler(ref arg2, out T2 arg2cs); + arg3Marshaler(ref arg3, out T3 arg3cs); + TResult resCs = cpy.Invoke(arg1cs, arg2cs, arg3cs); + resMarshaler(ref res, resCs); + } + catch (Exception ex) + { + exc.ToJS(ex); + } + }; + + + slot.Type = MarshalerType.Function; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(cb); } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int16.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int16.cs index f866e6e5775..aa1ea30fbfb 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int16.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int16.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out short value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.Int16Value; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(short value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Int16; + slot.Int16Value = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out short? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.Int16Value; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(short? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Int16; + slot.Int16Value = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int32.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int32.cs index aece8ae1db1..05ddab050c9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int32.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int32.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out int value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.Int32Value; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(int value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Int32; + slot.Int32Value = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out int? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.Int32Value; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(int? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Int32; + slot.Int32Value = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } /// <summary> @@ -53,7 +72,14 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToManaged(out int[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = new int[slot.Length]; + Marshal.Copy(slot.IntPtrValue, value, 0, slot.Length); + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -62,7 +88,16 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(int[]? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.Array; + slot.IntPtrValue = Marshal.AllocHGlobal(value.Length * sizeof(int)); + slot.Length = value.Length; + slot.ElementType = MarshalerType.Int32; + Marshal.Copy(value, 0, slot.IntPtrValue, slot.Length); } /// <summary> @@ -72,7 +107,10 @@ namespace System.Runtime.InteropServices.JavaScript // this only supports array round-trip public unsafe void ToManaged(out ArraySegment<int> value) { - throw new NotImplementedException(); + var array = (int[])((GCHandle)slot.GCHandle).Target!; + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)); + int byteOffset = (int)(slot.IntPtrValue - (nint)refPtr); + value = new ArraySegment<int>(array, byteOffset / sizeof(int), slot.Length); } /// <summary> @@ -81,7 +119,16 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToJS(ArraySegment<int> value) { - throw new NotImplementedException(); + if (value.Array == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.ArraySegment; + slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandleRef(value.Array, GCHandleType.Pinned); + var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array)); + slot.IntPtrValue = refPtr + (value.Offset * sizeof(int)); + slot.Length = value.Count; } /// <summary> @@ -90,7 +137,7 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public unsafe void ToManaged(out Span<int> value) { - throw new NotImplementedException(); + value = new Span<int>((void*)slot.IntPtrValue, slot.Length); } /// <summary> @@ -100,7 +147,9 @@ namespace System.Runtime.InteropServices.JavaScript /// <remarks>caller is responsible for pinning</remarks> public unsafe void ToJS(Span<int> value) { - throw new NotImplementedException(); + slot.Length = value.Length; + slot.IntPtrValue = (IntPtr)Unsafe.AsPointer(ref value.GetPinnableReference()); + slot.Type = MarshalerType.Span; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int52.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int52.cs index 1adbf33558b..a22902bf4e0 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int52.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Int52.cs @@ -7,6 +7,9 @@ namespace System.Runtime.InteropServices.JavaScript { public partial struct JSMarshalerArgument { + private const long I52_MAX_VALUE =((1L << 53) - 1); + private const long I52_MIN_VALUE =-I52_MAX_VALUE; + /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. @@ -14,7 +17,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out long value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = (long)slot.DoubleValue; } /// <summary> @@ -24,7 +32,13 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(long value) { - throw new NotImplementedException(); + if (value < I52_MIN_VALUE || value > I52_MAX_VALUE) + { + throw new OverflowException($"Overflow: value ${value} is out of ${I52_MIN_VALUE} ${I52_MAX_VALUE} range"); + } + + slot.Type = MarshalerType.Int52; + slot.DoubleValue = value; } /// <summary> @@ -34,7 +48,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out long? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = (long)slot.DoubleValue; } /// <summary> @@ -44,7 +63,19 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(long? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + if (value.Value < I52_MIN_VALUE || value.Value > I52_MAX_VALUE) + { + throw new OverflowException($"Overflow: value ${value} is out of ${I52_MIN_VALUE} ${I52_MAX_VALUE} range"); + } + slot.Type = MarshalerType.Int52; + slot.DoubleValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.IntPtr.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.IntPtr.cs index 5944a6f7af8..88a9776ecad 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.IntPtr.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.IntPtr.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out IntPtr value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.IntPtrValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(IntPtr value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.IntPtr; + slot.IntPtrValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out IntPtr? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.IntPtrValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(IntPtr? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.IntPtr; + slot.IntPtrValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } /// <summary> @@ -54,7 +73,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out void* value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = (byte*)slot.IntPtrValue; } /// <summary> @@ -64,7 +88,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(void* value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.IntPtr; + slot.IntPtrValue = (IntPtr)value; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs index 73f5de5501c..ea632d02db3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.JSObject.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.JavaScript { - // TODO this is only JSObject now, should it handle Uint8Array C# private type too ? public partial struct JSMarshalerArgument { /// <summary> @@ -15,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out JSObject? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = JavaScriptExports.CreateCSOwnedProxy(slot.JSHandle); } /// <summary> @@ -25,7 +29,20 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(JSObject? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + } + else + { + if (value.IsDisposed) + { + throw new ObjectDisposedException(nameof(value)); + } + slot.Type = MarshalerType.JSObject; + slot.JSHandle = value.JSHandle; + if (slot.JSHandle == IntPtr.Zero) throw new ObjectDisposedException(nameof(value)); + } } /// <summary> @@ -35,7 +52,22 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out JSObject?[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new JSObject?[slot.Length]; + JSMarshalerArgument* payload = (JSMarshalerArgument*)slot.IntPtrValue; + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + JSObject? val; + arg.ToManaged(out val); + value[i] = val; + } + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -45,7 +77,25 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(JSObject?[] value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Length = value.Length; + int bytes = value.Length * Marshal.SizeOf(typeof(JSMarshalerArgument)); + slot.Type = MarshalerType.Array; + slot.ElementType = MarshalerType.JSObject; + JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes); + Unsafe.InitBlock(payload, 0, (uint)bytes); + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + JSObject? val = value[i]; + arg.ToJS(val); + value[i] = val; + } + slot.IntPtrValue = (IntPtr)payload; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs index db8a5b22a3b..46ff369eecc 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Object.cs @@ -20,7 +20,88 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out object? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + } + else if (slot.Type == MarshalerType.Object) + { + value = ((GCHandle)slot.GCHandle).Target; + } + else if (slot.Type == MarshalerType.Boolean) + { + ToManaged(out bool v); + value = v; + } + else if (slot.Type == MarshalerType.Double) + { + ToManaged(out double v); + value = v; + } + else if (slot.Type == MarshalerType.JSObject) + { + ToManaged(out JSObject? val); + value = val; + } + else if (slot.Type == MarshalerType.String) + { + ToManaged(out string? val); + value = val; + } + else if (slot.Type == MarshalerType.Exception) + { + ToManaged(out Exception? val); + value = val; + } + else if (slot.Type == MarshalerType.DateTime) + { + ToManaged(out DateTime? val); + value = val; + } + else if (slot.Type == MarshalerType.JSException) + { + ToManaged(out Exception? val); + value = val; + } + else if (slot.Type == MarshalerType.Array) + { + if(slot.ElementType == MarshalerType.Byte) + { + ToManaged(out byte[]? val); + value = val; + } + else if (slot.ElementType == MarshalerType.Double) + { + ToManaged(out double[]? val); + value = val; + } + else if (slot.ElementType == MarshalerType.Int32) + { + ToManaged(out int[]? val); + value = val; + } + else if (slot.ElementType == MarshalerType.Object) + { + ToManaged(out object?[]? val); + value = val; + } + else + { + throw new NotImplementedException("ToManaged: " + slot.ElementType+ "[]"); + } + } + else if (slot.Type == MarshalerType.Task) + { + ToManaged(out Task<object?>? val, static (ref JSMarshalerArgument arg, out object? value) => + { + arg.ToManaged(out value); + }); + value = val; + } + else + { + throw new NotImplementedException("ToManaged: " + slot.Type); + } } /// <summary> @@ -30,7 +111,213 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(object? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + + Type type = value.GetType(); + if (type.IsPrimitive) + { + if (typeof(long) == type) + { + // we do it because not all Int64 could fit into Int52 of the JS Number + throw new NotImplementedException("ToJS: " + type.FullName); + } + else if (typeof(int) == type) + { + var v = (int)value; + ToJS(v); + } + else if (typeof(short) == type) + { + var v = (short)value; + ToJS(v); + } + else if (typeof(byte) == type) + { + var v = (byte)value; + ToJS(v); + } + else if (typeof(char) == type) + { + var v = (char)value; + ToJS(v); + } + else if (typeof(bool) == type) + { + var v = (bool)value; + ToJS(v); + } + else if (typeof(double) == type) + { + var v = (double)value; + ToJS(v); + } + else if (typeof(float) == type) + { + var v = (float)value; + ToJS(v); + } + else if (typeof(IntPtr) == type) + { + var v = (IntPtr)value; + ToJS(v); + } + else + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + } + else if (typeof(string) == type) + { + string? str = value as string; + ToJS(str); + } + else if (typeof(DateTimeOffset) == type) + { + var v = (DateTimeOffset)value; + ToJS(v); + } + else if (typeof(DateTime) == type) + { + var v = (DateTime)value; + ToJS(v); + } + else if (Nullable.GetUnderlyingType(type) is Type ut && ut != null) + { + if (typeof(long) == ut) + { + // we do it because not all Int64 could fit into Int52 of the JS Number + throw new NotImplementedException("ToJS: " + type.FullName); + } + else if (typeof(int) == ut) + { + var nv = value as int?; + ToJS(nv); + } + else if (typeof(short) == ut) + { + var nv = value as short?; + ToJS(nv); + } + else if (typeof(byte) == ut) + { + var nv = value as byte?; + ToJS(nv); + } + else if (typeof(char) == ut) + { + var nv = value as char?; + ToJS(nv); + } + else if (typeof(bool) == ut) + { + var nv = value as bool?; + ToJS(nv); + } + else if (typeof(double) == ut) + { + var nv = value as double?; + ToJS(nv); + } + else if (typeof(float) == ut) + { + var nv = value as float?; + ToJS(nv); + } + else if (typeof(IntPtr) == ut) + { + var nv = value as IntPtr?; + ToJS(nv); + } + else if (typeof(DateTimeOffset) == ut) + { + var nv = value as DateTimeOffset?; + ToJS(nv); + } + else if (typeof(DateTime) == ut) + { + var nv = value as DateTime?; + ToJS(nv); + } + else + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + } + else if (typeof(JSObject).IsAssignableFrom(type)) + { + JSObject? val = value as JSObject; + ToJS(val); + } + else if (typeof(Exception).IsAssignableFrom(type)) + { + Exception? val = value as Exception; + ToJS(val); + } + else if (typeof(Task<object>)==type) + { + Task<object>? val = value as Task<object>; + ToJS<object>(val, (ref JSMarshalerArgument arg, object value) => + { + object? valueRef= value; + arg.ToJS(valueRef); + }); + } + else if (typeof(Task).IsAssignableFrom(type)) + { + Task? val = value as Task; + ToJSDynamic(val); + } + else if (typeof(byte[]) == type) + { + byte[] val = (byte[])value; + ToJS(val); + slot.ElementType = MarshalerType.Byte; + } + else if (typeof(int[]) == type) + { + int[] val = (int[])value; + ToJS(val); + } + else if (typeof(double[]) == type) + { + double[] val = (double[])value; + ToJS(val); + } + else if (typeof(string[]) == type) + { + string[] val = (string[])value; + ToJS(val); + } + else if (typeof(object[]) == type) + { + object[] val = (object[])value; + ToJS(val); + } + else if (type.IsArray) + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + else if (typeof(MulticastDelegate).IsAssignableFrom(type.BaseType)) + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ArraySegment<>)) + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Span<>)) + { + throw new NotImplementedException("ToJS: " + type.FullName); + } + else + { + slot.Type = MarshalerType.Object; + slot.GCHandle = JavaScriptExports.GetJSOwnedObjectGCHandleRef(value); + } } /// <summary> @@ -40,8 +327,23 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out object?[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = new object?[slot.Length]; + JSMarshalerArgument* payload = (JSMarshalerArgument*)slot.IntPtrValue; + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + object? val; + arg.ToManaged(out val); + value[i] = val; + } + Interop.Runtime.DeregisterGCRoot(slot.IntPtrValue); + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -51,7 +353,26 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(object?[] value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Length = value.Length; + int bytes = value.Length * Marshal.SizeOf(typeof(JSMarshalerArgument)); + slot.Type = MarshalerType.Array; + JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes); + Unsafe.InitBlock(payload, 0, (uint)bytes); + Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero); + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + object? val = value[i]; + arg.ToJS(val); + value[i] = val; + } + slot.ElementType = MarshalerType.Object; + slot.IntPtrValue = (IntPtr)payload; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Single.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Single.cs index a83f3ee9ac3..08dfe11ed37 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Single.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Single.cs @@ -14,7 +14,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out float value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = default; + return; + } + value = slot.SingleValue; } /// <summary> @@ -24,7 +29,8 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(float value) { - throw new NotImplementedException(); + slot.Type = MarshalerType.Single; + slot.SingleValue = value; } /// <summary> @@ -34,7 +40,12 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out float? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + value = slot.SingleValue; } /// <summary> @@ -44,7 +55,15 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToJS(float? value) { - throw new NotImplementedException(); + if (value.HasValue) + { + slot.Type = MarshalerType.Single; + slot.SingleValue = value.Value; + } + else + { + slot.Type = MarshalerType.None; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs index de1b70acda4..afabd54c14e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.String.cs @@ -14,7 +14,16 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out string? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + fixed (void* argAsRoot = &slot.IntPtrValue) + { + value = Unsafe.AsRef<string>(argAsRoot); + } } /// <summary> @@ -24,7 +33,24 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(string? value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + } + else + { + slot.Type = MarshalerType.String; + // here we treat JSMarshalerArgument.IntPtrValue as root, because it's allocated on stack + // or we register the buffer with JSFunctionBinding._RegisterGCRoot + // We assume that GC would keep updating on GC move + // On JS side we wrap it with WasmExternalRoot + fixed (IntPtr* argAsRoot = &slot.IntPtrValue) + { + string cpy = value; + var currentRoot = (IntPtr*)Unsafe.AsPointer(ref cpy); + argAsRoot[0] = currentRoot[0]; + } + } } /// <summary> @@ -34,7 +60,23 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToManaged(out string?[]? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + value = new string?[slot.Length]; + JSMarshalerArgument* payload = (JSMarshalerArgument*)slot.IntPtrValue; + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + string? val; + arg.ToManaged(out val); + value[i] = val; + } + Interop.Runtime.DeregisterGCRoot(slot.IntPtrValue); + Marshal.FreeHGlobal(slot.IntPtrValue); } /// <summary> @@ -44,7 +86,26 @@ namespace System.Runtime.InteropServices.JavaScript [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ToJS(string?[] value) { - throw new NotImplementedException(); + if (value == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Length = value.Length; + int bytes = value.Length * Marshal.SizeOf(typeof(JSMarshalerArgument)); + slot.Type = MarshalerType.Array; + JSMarshalerArgument* payload = (JSMarshalerArgument*)Marshal.AllocHGlobal(bytes); + Unsafe.InitBlock(payload, 0, (uint)bytes); + Interop.Runtime.RegisterGCRoot((IntPtr)payload, bytes, IntPtr.Zero); + for (int i = 0; i < slot.Length; i++) + { + ref JSMarshalerArgument arg = ref payload[i]; + string? val = value[i]; + arg.ToJS(val); + value[i] = val; + } + slot.IntPtrValue = (IntPtr)payload; + slot.ElementType = MarshalerType.String; } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs index 8ee70ae7dfd..608443cc45c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.Task.cs @@ -25,18 +25,163 @@ namespace System.Runtime.InteropServices.JavaScript /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged(out Task value) + public unsafe void ToManaged(out Task? value) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + GCHandle gcHandle = (GCHandle)slot.GCHandle; + JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; + if (holder == null) throw new NullReferenceException("JSHostImplementation.TaskCallback"); + + TaskCompletionSource tcs = new TaskCompletionSource(gcHandle); + JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + { + ref JSMarshalerArgument arg_exception = ref arguments_buffer[0]; + try + { + if (arg_exception.slot.Type != MarshalerType.None) + { + arg_exception.ToManaged(out Exception? fail); + tcs.SetException(fail!); + } + else + { + tcs.SetResult(); + } + arg_exception.slot.Type = MarshalerType.None; + } + catch (Exception ex) + { + arg_exception.ToJS(ex); + } + }; + holder.Callback = callback; + value = tcs.Task; } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public unsafe void ToManaged<T>(out Task<T> value, ArgumentToManagedCallback<T> marshaler) + public unsafe void ToManaged<T>(out Task<T>? value, ArgumentToManagedCallback<T> marshaler) { - throw new NotImplementedException(); + if (slot.Type == MarshalerType.None) + { + value = null; + return; + } + + GCHandle gcHandle = (GCHandle)slot.GCHandle; + JSHostImplementation.TaskCallback? holder = (JSHostImplementation.TaskCallback?)gcHandle.Target; + if (holder == null) throw new NullReferenceException("JSHostImplementation.TaskCallback"); + + TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(gcHandle); + JSHostImplementation.ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) => + { + ref JSMarshalerArgument arg_exception = ref arguments_buffer[0]; + ref JSMarshalerArgument arg1 = ref arguments_buffer[2]; + try + { + if (arg_exception.slot.Type != MarshalerType.None) + { + arg_exception.ToManaged(out Exception? fail); + if (fail == null) throw new NullReferenceException("Exception"); + tcs.SetException(fail); + } + else + { + marshaler(ref arg1, out T result); + tcs.SetResult(result); + } + arg_exception.slot.Type = MarshalerType.None; + } + catch (Exception ex) + { + arg_exception.ToJS(ex); + } + }; + holder.Callback = callback; + value = tcs.Task; + } + + internal void ToJSDynamic(Task? value) + { + Task? task = value; + + if (task == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.Task; + + if (task.IsCompleted) + { + if (task.Exception != null) + { + Exception ex = task.Exception; + slot.JSHandle = CreateFailedPromise(ex); + return; + } + else + { + object? result = JSHostImplementation.GetTaskResult(task); + slot.JSHandle = CreateResolvedPromise(result, MarshalResult); + return; + } + } + + + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + + task.GetAwaiter().OnCompleted(Complete); + + /* TODO multi-threading + * tasks could resolve on any thread and so this code will have race condition between task.IsCompleted and OnCompleted(Complete) callback + * This probably needs SynchronizationContext to marshal this call to main thread + */ + Debug.Assert(!task.IsCompleted, "multithreading race condition"); + + void Complete() + { + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try + { + if (task.Exception != null) + { + FailPromise(promise, task.Exception); + } + else + { + object? result = JSHostImplementation.GetTaskResult(task); + + ResolvePromise(promise, result, MarshalResult); + } + } + catch (Exception ex) + { + throw new InvalidProgramException(ex.Message, ex); + } + finally + { + // this should never happen after the task was GC'd + promise.Dispose(); + } + } + + static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult) + { + arg.ToJS(taskResult); + } } /// <summary> @@ -45,16 +190,283 @@ namespace System.Runtime.InteropServices.JavaScript /// </summary> public void ToJS(Task value) { - throw new NotImplementedException(); + Task task = value; + + if (task == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.Task; + + if (task.IsCompleted) + { + if (task.Exception != null) + { + Exception ex = task.Exception; + slot.JSHandle = CreateFailedPromise(ex); + return; + } + else + { + slot.JSHandle = IntPtr.Zero; + return; + } + } + + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + + task.GetAwaiter().OnCompleted(Complete); + + /* TODO multi-threading + * tasks could resolve on any thread and so this code will have race condition between task.IsCompleted and OnCompleted(Complete) callback + * This probably needs SynchronizationContext to marshal this call to main thread + */ + Debug.Assert(!task.IsCompleted, "multithreading race condition"); + + void Complete() + { + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try + { + if (task.Exception != null) + { + FailPromise(promise, task.Exception); + } + else + { + ResolveVoidPromise(promise); + } + } + catch (Exception ex) + { + throw new InvalidProgramException(ex.Message, ex); + } + finally + { + // this should never happen after the task was GC'd + promise.Dispose(); + } + } } /// <summary> /// Implementation of the argument marshaling. /// It's used by JSImport code generator and should not be used by developers in source code. /// </summary> - public void ToJS<T>(Task<T> value, ArgumentToJSCallback<T> marshaler) + public void ToJS<T>(Task<T>? value, ArgumentToJSCallback<T> marshaler) + { + Task<T>? task = value; + + if (task == null) + { + slot.Type = MarshalerType.None; + return; + } + slot.Type = MarshalerType.Task; + + if (task.IsCompleted) + { + if (task.Exception != null) + { + Exception ex = task.Exception; + slot.JSHandle = CreateFailedPromise(ex); + return; + } + else + { + T result = task.Result; + slot.JSHandle = CreateResolvedPromise(result, marshaler); + return; + } + } + + + IntPtr jsHandle = CreatePendingPromise(); + slot.JSHandle = jsHandle; + JSObject promise = JavaScriptExports.CreateCSOwnedProxy(jsHandle); + + task.GetAwaiter().OnCompleted(Complete); + + /* TODO multi-threading + * tasks could resolve on any thread and so this code will have race condition between task.IsCompleted and OnCompleted(Complete) callback + * This probably needs SynchronizationContext to marshal this call to main thread + */ + Debug.Assert(!task.IsCompleted, "multithreading race condition"); + + void Complete() + { + // When this task was never resolved/rejected + // promise (held by this lambda) would be collected by GC after the Task is collected + // and would also allow the JS promise to be collected + + try + { + if (task.Exception != null) + { + FailPromise(promise, task.Exception); + } + else + { + T result = task.Result; + ResolvePromise(promise, result, marshaler); + } + } + catch (Exception ex) + { + throw new InvalidProgramException(ex.Message, ex); + } + finally + { + // this should never happen after the task was GC'd + promise.Dispose(); + } + } + } + + private static IntPtr CreatePendingPromise() { - throw new NotImplementedException(); + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + arg_value.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + arg_value.slot.Type = MarshalerType.Task; + + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; + } + + private static IntPtr CreateFailedPromise(Exception ex) + { + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + res.Initialize(); + arg_value.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + // should fail it with exception + exc.ToJS(ex); + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; + } + + private static void FailPromise(JSObject promise, Exception ex) + { + if (promise.IsDisposed) + { + throw new ObjectDisposedException(nameof(promise)); + } + + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + arg_value.Initialize(); + + // should update existing promise + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; + + // should fail it with exception + exc.ToJS(ex); + + JavaScriptImports.MarshalPromise(args); + } + + private static IntPtr CreateResolvedPromise<T>(T value, ArgumentToJSCallback<T> marshaler) + { + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + + // should create new promise + arg_handle.slot.Type = MarshalerType.Task; + arg_handle.slot.JSHandle = IntPtr.Zero; + + // and resolve it with value + marshaler(ref arg_value, value); + + JavaScriptImports.MarshalPromise(args); + return res.slot.JSHandle; + } + + private static void ResolveVoidPromise(JSObject promise) + { + if (promise.IsDisposed) + { + throw new ObjectDisposedException(nameof(promise)); + } + + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + + // should update existing promise + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; + + arg_value.slot.Type = MarshalerType.None; + + JavaScriptImports.MarshalPromise(args); + } + + private static void ResolvePromise<T>(JSObject promise, T value, ArgumentToJSCallback<T> marshaler) + { + if (promise.IsDisposed) + { + throw new ObjectDisposedException(nameof(promise)); + } + + Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4]; + ref JSMarshalerArgument exc = ref args[0]; + ref JSMarshalerArgument res = ref args[1]; + ref JSMarshalerArgument arg_handle = ref args[2]; + ref JSMarshalerArgument arg_value = ref args[3]; + + exc.Initialize(); + res.Initialize(); + + // should update existing promise + arg_handle.slot.Type = MarshalerType.None; + arg_handle.slot.JSHandle = promise.JSHandle; + + // and resolve it with value + marshaler(ref arg_value, value); + + JavaScriptImports.MarshalPromise(args); } } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs index 73c688df6ce..003881e5c01 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs @@ -39,7 +39,7 @@ namespace JSImportGenerator.Unit.Tests new Microsoft.Interop.JavaScript.JSImportGenerator(), new Microsoft.Interop.JavaScript.JSExportGenerator()); - JSTestUtils.DumpCode(source, newComp, generatorDiags); + // uncomment for debugging JSTestUtils.DumpCode(source, newComp, generatorDiags); Assert.Empty(generatorDiags); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Fails.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Fails.cs index f0f2f1ddb7f..c7f98039414 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Fails.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Fails.cs @@ -61,7 +61,7 @@ namespace JSImportGenerator.Unit.Tests var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.JavaScript.JSImportGenerator(), new Microsoft.Interop.JavaScript.JSExportGenerator()); - JSTestUtils.DumpCode(source, newComp, generatorDiags); + // uncomment for debugging JSTestUtils.DumpCode(source, newComp, generatorDiags); if (generatorMessages != null) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj index 0e254bcb909..73d8ac230d7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/JSImportGenerator.Unit.Tests.csproj @@ -1,11 +1,9 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> <TargetFramework>$(NetCoreAppCurrent)</TargetFramework> <Nullable>enable</Nullable> <TestRunRequiresLiveRefPack>true</TestRunRequiresLiveRefPack> </PropertyGroup> - <ItemGroup> <Compile Include="$(CommonTestPath)SourceGenerators\LiveReferencePack.cs" Link="Common\SourceGenerators\LiveReferencePack.cs" /> <Compile Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\tests\LibraryImportGenerator.UnitTests\TestUtils.cs" Link="LibraryImportGenerator\TestUtils.cs" /> @@ -14,19 +12,12 @@ <Compile Include="Compiles.cs" /> <Compile Include="JSTestUtils.cs" /> </ItemGroup> - <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="$(CompilerPlatformTestingVersion)" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(CompilerPlatformTestingVersion)" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\tests\Ancillary.Interop\Ancillary.Interop.csproj" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\tests\Ancillary.Interop\Ancillary.Interop.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" /> - </ItemGroup> - - <ItemGroup> <None Include="$(RepoRoot)/NuGet.config" Link="NuGet.config" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> </Project> diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Tests.csproj new file mode 100644 index 00000000000..e5e80df11f2 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <TargetFrameworks>$(NetCoreAppCurrent)-Browser</TargetFrameworks> + <TestRuntime>true</TestRuntime> + <WasmXHarnessArgs>$(WasmXHarnessArgs) --engine-arg=--expose-gc --web-server-use-cop</WasmXHarnessArgs> + <!-- Use following lines to write the generated files to disk. --> + <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> + </PropertyGroup> + <ItemGroup> + <Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.cs" /> + <Compile Include="System\Runtime\InteropServices\JavaScript\JSImportExportTest.cs" /> + <Compile Include="System\Runtime\InteropServices\JavaScript\Utils.cs" /> + <None Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" /> + <None Include="$(CompilerGeneratedFilesOutputPath)\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JSImportGenerator\JSImports.g.cs" /> + <None Include="$(CompilerGeneratedFilesOutputPath)\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" /> + <WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" /> + </ItemGroup> +</Project> diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs new file mode 100644 index 00000000000..b2595736311 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -0,0 +1,1929 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public class JSImportExportTest : IAsyncLifetime + { + [Fact] + public unsafe void StructSize() + { + Assert.Equal(16, sizeof(JSMarshalerArgument)); + } + + [Fact] + public unsafe void GlobalThis() + { + Assert.Null(JSHost.GlobalThis.GetPropertyAsString("dummy")); + Assert.False(JSHost.GlobalThis.HasProperty("dummy")); + Assert.Equal("undefined", JSHost.GlobalThis.GetTypeOfProperty("dummy")); + Assert.Equal("function", JSHost.GlobalThis.GetTypeOfProperty("Array")); + Assert.NotNull(JSHost.GlobalThis.GetPropertyAsJSObject("javaScriptTestHelper")); + } + + [Fact] + public unsafe void DotnetInstance() + { + Assert.True(JSHost.DotnetInstance.HasProperty("MONO")); + Assert.Equal("object", JSHost.DotnetInstance.GetTypeOfProperty("MONO")); + + JSHost.DotnetInstance.SetProperty("testBool", true); + Assert.Equal("boolean", JSHost.DotnetInstance.GetTypeOfProperty("testBool")); + + JSHost.DotnetInstance.SetProperty("testInt", 42); + Assert.Equal("number", JSHost.DotnetInstance.GetTypeOfProperty("testInt")); + Assert.Equal(42, JSHost.DotnetInstance.GetPropertyAsInt32("testInt")); + + JSHost.DotnetInstance.SetProperty("testDouble", 3.14); + Assert.Equal("number", JSHost.DotnetInstance.GetTypeOfProperty("testDouble")); + Assert.Equal(3.14, JSHost.DotnetInstance.GetPropertyAsDouble("testDouble")); + + JSHost.DotnetInstance.SetProperty("testString", "Yoda"); + Assert.Equal("string", JSHost.DotnetInstance.GetTypeOfProperty("testString")); + Assert.Equal("Yoda", JSHost.DotnetInstance.GetPropertyAsString("testString")); + } + + [Fact] + public unsafe void BadCast() + { + JSException ex; + JSHost.DotnetInstance.SetProperty("testBool", true); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsInt32("testBool")); + Assert.Contains("Value is not an integer", ex.Message); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsDouble("testBool")); + Assert.Contains("Value is not a Number", ex.Message); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsString("testBool")); + Assert.Contains("Value is not a String", ex.Message); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsJSObject("testBool")); + Assert.Contains("JSObject proxy of boolean is not supported", ex.Message); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsByteArray("testBool")); + Assert.Contains("Value is not an Array or Uint8Array", ex.Message); + JSHost.DotnetInstance.SetProperty("testInt", 42); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsBoolean("testInt")); + Assert.Contains("Value is not a Boolean", ex.Message); + } + + [Fact] + public unsafe void OutOfRange() + { + JSException ex; + JSHost.DotnetInstance.SetProperty("testDouble", 9007199254740991L); + ex = Assert.Throws<JSException>(() => JSHost.DotnetInstance.GetPropertyAsInt32("testDouble")); + Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message); + } + + + #region Get/Set Property + + [Fact] + public unsafe void JSObjectGetSet() + { + Func<double, JSObject> createObject = Utils.CreateFunctionDoubleJSObject("a", @" + var x = {a, x:42 }; + return x; + "); + JSObject obj = createObject(1); + Assert.NotNull(obj); + double? a = obj.GetPropertyAsDouble("a"); + Assert.Equal(1, a); + + double? x = obj.GetPropertyAsDouble("x"); + Assert.Equal(42, x); + + int? xi = obj.GetPropertyAsInt32("x"); + Assert.Equal(42, xi); + + /* + obj.GetProperty("x", out string? xs); + Assert.Equal("42", xs); + */ + + obj.SetProperty("b", 3); + double? b = obj.GetPropertyAsDouble("b"); + Assert.Equal(3, b); + + obj.SetProperty("c", "test"); + string? c = obj.GetPropertyAsString("c"); + Assert.Equal("test", c); + + obj.SetProperty("c", (string)null); + string? d = obj.GetPropertyAsString("c"); + Assert.Null(d); + } + + #endregion + + #region CreateFunction + + [Fact] + public unsafe void CreateFunctionDouble() + { + Func<double, double, double> doublePlus = Utils.CreateFunctionDoubleDoubleDouble("a", "b", "return a+b"); + Assert.Equal(3, doublePlus(1, 2)); + Assert.Equal(Math.PI * 2, doublePlus(Math.PI, Math.PI)); + } + + [Fact] + public unsafe void CreateFunctionDoubleThrow() + { + Func<double, double, double> doubleThrows = Utils.CreateFunctionDoubleDoubleDouble("a", "b", "throw Error('test '+a+' '+b);"); + var ex = Assert.Throws<JSException>(() => doubleThrows(1, 2)); + Assert.Equal("Error: test 1 2", ex.Message); + Assert.Contains("create_function", ex.StackTrace); + } + + [Fact] + public unsafe void CreateFunctionString() + { + Func<string, string, string> stringPlus = Utils.CreateFunctionStringStringString("a", "b", "return a+b"); + Assert.Equal("hello world", stringPlus("hello ", "world")); + Assert.Equal("hellonull", stringPlus("hello", null)); + } + + [Fact] + public unsafe void CreateFunctionInternal() + { + Func<string> internals = Utils.CreateFunctionString("return INTERNAL.BINDING_ASM"); + Assert.Equal("[System.Runtime.InteropServices.JavaScript]System.Runtime.InteropServices.JavaScript.JavaScriptExports", internals()); + } + + #endregion + + #region Arrays + + public static IEnumerable<object[]> MarshalByteArrayCases() + { + yield return new object[] { new byte[] { 1, 2, 3, byte.MaxValue, byte.MinValue } }; + yield return new object[] { new byte[] { } }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(MarshalByteArrayCases))] + public unsafe void JsImportByteArray(byte[]? expected) + { + var actual = JavaScriptTestHelper.echo1_ByteArray(expected); + Assert.Equal(expected, actual); + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_ByteArray(expected, i); + Assert.Equal(expected[i], actualI); + } + } + + public static IEnumerable<object[]> MarshalIntArrayCases() + { + yield return new object[] { new int[] { 1, 2, 3, int.MaxValue, int.MinValue } }; + yield return new object[] { new int[] { } }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(MarshalIntArrayCases))] + public unsafe void JsImportIntArray(int[]? expected) + { + var actual = JavaScriptTestHelper.echo1_Int32Array(expected); + Assert.Equal(expected, actual); + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_Int32Array(expected, i); + Assert.Equal(expected[i], actualI); + } + } + + public static IEnumerable<object[]> MarshalDoubleArrayCases() + { + yield return new object[] { new double[] { 1, 2, 3, double.MaxValue, double.MinValue, double.Pi, double.NegativeInfinity, double.PositiveInfinity, double.NaN } }; + yield return new object[] { new double[] { } }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(MarshalDoubleArrayCases))] + public unsafe void JsImportDoubleArray(double[]? expected) + { + var actual = JavaScriptTestHelper.echo1_DoubleArray(expected); + Assert.Equal(expected, actual); + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_DoubleArray(expected, i); + Assert.Equal(expected[i], actualI); + } + } + + public static IEnumerable<object[]> MarshalStringArrayCases() + { + yield return new object[] { new string[] { "\u0050\u0159\u00ed\u006c\u0069\u0161", "\u017e\u006c\u0075\u0165\u006f\u0075\u010d\u006b\u00fd" } }; + yield return new object[] { new string[] { string.Intern("hello"), string.Empty, null } }; + yield return new object[] { new string[] { } }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(MarshalStringArrayCases))] + public unsafe void JsImportStringArray(string[]? expected) + { + var actual = JavaScriptTestHelper.echo1_StringArray(expected); + Assert.Equal(expected, actual); + + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_StringArray(expected, i); + Assert.Equal(expected[i], actualI); + } + } + + public class SomethingRef + { + } + + public class SomethingStruct + { + } + + public static IEnumerable<object[]> MarshalObjectArrayCases() + { + yield return new object[] { new object[] { string.Intern("hello"), string.Empty } }; + yield return new object[] { new object[] { 1.1d, new DateTime(2022, 5, 8, 14, 55, 01, DateTimeKind.Utc), false, true } }; + yield return new object[] { new object[] { new double?(1.1d), new DateTime?(new DateTime(2022, 5, 8, 14, 55, 01, DateTimeKind.Utc)), new bool?(false), new bool?(true) } }; + yield return new object[] { new object[] { null, new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } }; + yield return new object[] { new object[] { JavaScriptTestHelper.createData("test"), JavaScriptTestHelper.createException("test") } }; + yield return new object[] { new object[] { new byte[] { }, new int[] { }, new double[] { }, new string[] { }, new object[] { } } }; + yield return new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" }, new object[] { } } }; + yield return new object[] { new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" } , new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } } }; + yield return new object[] { new object[] { } }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(MarshalObjectArrayCases))] + public unsafe void JsImportObjectArray(object[]? expected) + { + var actual = JavaScriptTestHelper.echo1_ObjectArray(expected); + Assert.Equal(expected, actual); + + + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i); + Assert.Equal(expected[i], actualI); + } + } + + public static IEnumerable<object[]> MarshalObjectArrayCasesToDouble() + { + yield return new object[] { new object[] { (byte)42 } }; + yield return new object[] { new object[] { (short)42 } }; + yield return new object[] { new object[] { 42 } }; + yield return new object[] { new object[] { 3.14f } }; + yield return new object[] { new object[] { 'A' } }; + } + + [Theory] + [MemberData(nameof(MarshalObjectArrayCasesToDouble))] + public unsafe void JsImportObjectArrayToDouble(object[]? expected) + { + if (expected != null) for (int i = 0; i < expected.Length; i++) + { + var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i); + if (expected[i].GetType() == typeof(char)) + { + Assert.Equal((double)(int)(char)expected[i], actualI); + } + else + { + Assert.Equal(Convert.ToDouble(expected[i]), actualI); + } + } + } + + public static IEnumerable<object[]> MarshalObjectArrayCasesThrow() + { + yield return new object[] { new object[] { () => { } } }; + yield return new object[] { new object[] { (int a) => { } } }; + yield return new object[] { new object[] { (int a) => { return a; } } }; + yield return new object[] { new object[] { (dummyDelegate)dummyDelegateA } }; + yield return new object[] { new object[] { 0L } }; + yield return new object[] { new object[] { 0UL } }; + yield return new object[] { new object[] { (sbyte)0 } }; + yield return new object[] { new object[] { (ushort)0 } }; + yield return new object[] { new object[] { new SomethingStruct[] { } } }; + yield return new object[] { new object[] { new SomethingRef[] { }, } }; + yield return new object[] { new object[] { new ArraySegment<byte>(new byte[] { 11 }) , } }; + } + delegate void dummyDelegate(); + static void dummyDelegateA() + { + } + + [Theory] + [MemberData(nameof(MarshalObjectArrayCasesThrow))] + public unsafe void JsImportObjectArrayThrows(object[]? expected) + { + Assert.Throws<NotImplementedException>(() => JavaScriptTestHelper.echo1_ObjectArray(expected)); + } + + [Fact] + public async Task JsImportObjectArrayTask() + { + object[] expected = new object[] { Task.CompletedTask }; + var actual = JavaScriptTestHelper.echo1_ObjectArray(expected); + Assert.True(typeof(Task).IsAssignableFrom(actual[0].GetType())); + await Task.Delay(100); + await Task.Yield(); + var actual0 = actual[0] as Task; + Assert.True(actual0.IsCompleted); + Assert.True(actual0.IsCompletedSuccessfully); + var actualT = JavaScriptTestHelper.store_ObjectArray(expected, 0); + await Task.Delay(100); + await Task.Yield(); + var actualT0 = actualT as Task; + Assert.True(actualT0.IsCompletedSuccessfully); + } + + [Fact] + public async Task JsImportObjectArrayTaskObject() + { + object[] expected = new object[] { Task.FromResult((object)42) }; + var actual = JavaScriptTestHelper.echo1_ObjectArray(expected); + var actual0 = Assert.IsType<Task<object>>(actual[0]); + await Task.Delay(100); + await Task.Yield(); + Assert.True(actual0.IsCompleted); + Assert.True(actual0.IsCompletedSuccessfully); + Assert.Equal(42.0d, actual0.Result); + } + + [Fact] + public async Task JsImportObjectArrayTaskObjectFail() + { + var exex = new Exception("test"); + object[] expected = new object[] { Task.FromException(exex) }; + var actual = JavaScriptTestHelper.echo1_ObjectArray(expected); + var actual0 = Assert.IsType<Task<object>>(actual[0]); + await Task.Delay(100); + await Task.Yield(); + Assert.True(actual0.IsCompleted); + Assert.True(actual0.IsFaulted); + var actualEx = await Assert.ThrowsAsync<Exception>(async () => await actual0); + Assert.Same(exex, actualEx); + } + + #endregion + + #region Views + + [Fact] + public unsafe void JsImportSpanOfByte() + { + var expectedBytes = stackalloc byte[] { 1, 2, 42, 0, 127, 255 }; + Span<byte> expected = new Span<byte>(expectedBytes, 6); + Assert.True(Unsafe.AsPointer(ref expected.GetPinnableReference()) == expectedBytes); + Span<byte> actual = JavaScriptTestHelper.echo1_SpanOfByte(expected, false); + Assert.Equal(expected.Length, actual.Length); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.GetPinnableReference(), actual.GetPinnableReference()); + Assert.True(actual.SequenceCompareTo(expected) == 0); + Assert.Equal(expected.ToArray(), actual.ToArray()); + actual = JavaScriptTestHelper.echo1_SpanOfByte(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + [Fact] + public unsafe void JsImportSpanOfInt32() + { + var expectedBytes = stackalloc int[] { 0, 1, -2, 42, int.MaxValue, int.MinValue }; + Span<int> expected = new Span<int>(expectedBytes, 6); + Assert.True(Unsafe.AsPointer(ref expected.GetPinnableReference()) == expectedBytes); + Span<int> actual = JavaScriptTestHelper.echo1_SpanOfInt32(expected, false); + Assert.Equal(expected.Length, actual.Length); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.GetPinnableReference(), actual.GetPinnableReference()); + Assert.True(actual.SequenceCompareTo(expected) == 0); + Assert.Equal(expected.ToArray(), actual.ToArray()); + actual = JavaScriptTestHelper.echo1_SpanOfInt32(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + [Fact] + public unsafe void JsImportSpanOfDouble() + { + var expectedBytes = stackalloc double[] { 0, 1, -1, double.Pi, 42, double.MaxValue, double.MinValue, double.NaN, double.PositiveInfinity, double.NegativeInfinity }; + Span<double> expected = new Span<double>(expectedBytes, 10); + Assert.True(Unsafe.AsPointer(ref expected.GetPinnableReference()) == expectedBytes); + Span<double> actual = JavaScriptTestHelper.echo1_SpanOfDouble(expected, false); + Assert.Equal(expected.Length, actual.Length); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.GetPinnableReference(), actual.GetPinnableReference()); + Assert.True(actual.SequenceCompareTo(expected) == 0); + Assert.Equal(expected.ToArray(), actual.ToArray()); + actual = JavaScriptTestHelper.echo1_SpanOfDouble(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + [Fact] + public unsafe void JsImportArraySegmentOfByte() + { + var expectedBytes = new byte[] { 88, 1, 2, 42, 0, 127, 255 }; + ArraySegment<byte> expected = new ArraySegment<byte>(expectedBytes, 1, 6); + ArraySegment<byte> actual = JavaScriptTestHelper.echo1_ArraySegmentOfByte(expected, false); + Assert.Equal(expected.Count, actual.Count); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.Array, actual.Array); + actual = JavaScriptTestHelper.echo1_ArraySegmentOfByte(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + [Fact] + public unsafe void JsImportArraySegmentOfInt32() + { + var expectedBytes = new int[] { 88, 0, 1, -2, 42, int.MaxValue, int.MinValue }; + ArraySegment<int> expected = new ArraySegment<int>(expectedBytes, 1, 6); + ArraySegment<int> actual = JavaScriptTestHelper.echo1_ArraySegmentOfInt32(expected, false); + Assert.Equal(expected.Count, actual.Count); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.Array, actual.Array); + actual = JavaScriptTestHelper.echo1_ArraySegmentOfInt32(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + [Fact] + public unsafe void JsImportArraySegmentOfDouble() + { + var expectedBytes = new double[] { 88.88, 0, 1, -1, double.Pi, 42, double.MaxValue, double.MinValue, double.NaN, double.PositiveInfinity, double.NegativeInfinity }; + ArraySegment<double> expected = new ArraySegment<double>(expectedBytes, 1, 10); + ArraySegment<double> actual = JavaScriptTestHelper.echo1_ArraySegmentOfDouble(expected, false); + Assert.Equal(expected.Count, actual.Count); + Assert.NotEqual(expected[0], expected[1]); + Assert.Equal(expected.Array, actual.Array); + actual = JavaScriptTestHelper.echo1_ArraySegmentOfDouble(expected, true); + Assert.Equal(expected[0], expected[1]); + Assert.Equal(actual[0], actual[1]); + } + + #endregion + + #region Boolean + public static IEnumerable<object[]> MarshalBooleanCases() + { + yield return new object[] { true }; + yield return new object[] { false }; + } + + [Theory] + [MemberData(nameof(MarshalBooleanCases))] + public void JsImportBoolean(bool value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Boolean, + JavaScriptTestHelper.retrieve1_Boolean, + JavaScriptTestHelper.echo1_Boolean, + JavaScriptTestHelper.throw1_Boolean, + JavaScriptTestHelper.identity1_Boolean, + "boolean"); + } + + [Theory] + [MemberData(nameof(MarshalBooleanCases))] + public void JsExportBoolean(bool value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Boolean, + nameof(JavaScriptTestHelper.EchoBoolean), + "boolean"); + } + #endregion Boolean + + #region Char + public static IEnumerable<object[]> MarshalCharCases() + { + yield return new object[] { (char)42 }; + yield return new object[] { (char)1 }; + yield return new object[] { 'Ž' }; + yield return new object[] { '♡' }; + yield return new object[] { char.MaxValue }; + yield return new object[] { char.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalCharCases))] + public void JsImportChar(char value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Char, + JavaScriptTestHelper.retrieve1_Char, + JavaScriptTestHelper.echo1_Char, + JavaScriptTestHelper.throw1_Char, + JavaScriptTestHelper.identity1_Char, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalCharCases))] + public void JsExportChar(char value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Char, + nameof(JavaScriptTestHelper.EchoChar), + "number"); + } + #endregion Char + + #region Byte + public static IEnumerable<object[]> MarshalByteCases() + { + yield return new object[] { (byte)42 }; + yield return new object[] { (byte)1 }; + yield return new object[] { byte.MaxValue }; + yield return new object[] { byte.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalByteCases))] + public void JsImportByte(byte value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Byte, + JavaScriptTestHelper.retrieve1_Byte, + JavaScriptTestHelper.echo1_Byte, + JavaScriptTestHelper.throw1_Byte, + JavaScriptTestHelper.identity1_Byte, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalByteCases))] + public void JsExportByte(byte value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Byte, + nameof(JavaScriptTestHelper.EchoByte), + "number"); + } + + [Theory] + [MemberData(nameof(OutOfRangeCases))] + public void ByteOutOfRange(double value, string message) + { + JavaScriptTestHelper.store1_Double(value); + var ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.retrieve1_Byte()); + Assert.Contains(message, ex.Message); + } + + #endregion Byte + + #region Int16 + public static IEnumerable<object[]> MarshalInt16Cases() + { + yield return new object[] { 42 }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { -1 }; + yield return new object[] { short.MaxValue }; + yield return new object[] { short.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalInt16Cases))] + public void JsImportInt16(short value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Int16, + JavaScriptTestHelper.retrieve1_Int16, + JavaScriptTestHelper.echo1_Int16, + JavaScriptTestHelper.throw1_Int16, + JavaScriptTestHelper.identity1_Int16, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalInt16Cases))] + public void JsExportInt16(short value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Int16, + nameof(JavaScriptTestHelper.EchoInt16), + "number"); + } + #endregion Int16 + + #region Int32 + public static IEnumerable<object[]> MarshalInt32Cases() + { + yield return new object[] { 42 }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { -1 }; + yield return new object[] { int.MaxValue }; + yield return new object[] { int.MinValue }; + } + + public static IEnumerable<object[]> OutOfRangeCases() + { + yield return new object[] { double.MaxValue, "Value is not an integer" }; + yield return new object[] { double.MinValue, "Value is not an integer" }; + yield return new object[] { double.NaN, "Value is not an integer" }; + yield return new object[] { double.NegativeInfinity, "Value is not an integer" }; + yield return new object[] { double.PositiveInfinity, "Value is not an integer" }; + yield return new object[] { (double)MAX_SAFE_INTEGER, "Overflow" }; + } + + [Theory] + [MemberData(nameof(MarshalInt32Cases))] + public void JsImportInt32(int value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Int32, + JavaScriptTestHelper.retrieve1_Int32, + JavaScriptTestHelper.echo1_Int32, + JavaScriptTestHelper.throw1_Int32, + JavaScriptTestHelper.identity1_Int32, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalInt32Cases))] + public void JsExportInt32(int value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Int32, + nameof(JavaScriptTestHelper.EchoInt32), + "number"); + } + + [Theory] + [MemberData(nameof(OutOfRangeCases))] + public void Int32OutOfRange(double value, string message) + { + JavaScriptTestHelper.store1_Double(value); + var ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.retrieve1_Int32()); + Assert.Contains(message, ex.Message); + } + + #endregion Int32 + + #region Int52 + const long MAX_SAFE_INTEGER = 9007199254740991L;// Number.MAX_SAFE_INTEGER + const long MIN_SAFE_INTEGER = -9007199254740991L;// Number.MIN_SAFE_INTEGER + public static IEnumerable<object[]> MarshalInt52Cases() + { + yield return new object[] { -1 }; + yield return new object[] { 42 }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { MAX_SAFE_INTEGER }; + yield return new object[] { MIN_SAFE_INTEGER }; + } + + [Theory] + [MemberData(nameof(MarshalInt52Cases))] + public void JsImportInt52(long value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Int52, + JavaScriptTestHelper.retrieve1_Int52, + JavaScriptTestHelper.echo1_Int52, + JavaScriptTestHelper.throw1_Int52, + JavaScriptTestHelper.identity1_Int52, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalInt52Cases))] + public void JsExportInt52(long value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Int52, + nameof(JavaScriptTestHelper.EchoInt52), + "number"); + } + #endregion Int52 + + #region BigInt64 + public static IEnumerable<object[]> MarshalBigInt64Cases() + { + yield return new object[] { -1 }; + yield return new object[] { 42 }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { MAX_SAFE_INTEGER }; + yield return new object[] { MIN_SAFE_INTEGER }; + yield return new object[] { long.MinValue }; + yield return new object[] { long.MaxValue }; + } + + [Theory] + [MemberData(nameof(MarshalBigInt64Cases))] + public void JsImportBigInt64(long value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_BigInt64, + JavaScriptTestHelper.retrieve1_BigInt64, + JavaScriptTestHelper.echo1_BigInt64, + JavaScriptTestHelper.throw1_BigInt64, + JavaScriptTestHelper.identity1_BigInt64, + "bigint"); + } + + [Theory] + [MemberData(nameof(MarshalBigInt64Cases))] + public void JsExportBigInt64(long value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_BigInt64, + nameof(JavaScriptTestHelper.EchoBigInt64), + "bigint"); + } + #endregion BigInt64 + + #region Double + public static IEnumerable<object[]> MarshalDoubleCases() + { + yield return new object[] { Math.PI }; + yield return new object[] { 0.0 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.MinValue }; + yield return new object[] { double.NegativeInfinity }; + yield return new object[] { double.PositiveInfinity }; + yield return new object[] { double.NaN }; + } + + [Theory] + [MemberData(nameof(MarshalDoubleCases))] + public void JsImportDouble(double value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Double, + JavaScriptTestHelper.retrieve1_Double, + JavaScriptTestHelper.echo1_Double, + JavaScriptTestHelper.throw1_Double, + JavaScriptTestHelper.identity1_Double, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalDoubleCases))] + public void JsExportDouble(double value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Double, + nameof(JavaScriptTestHelper.EchoDouble), + "number"); + } + #endregion Double + + #region Single + public static IEnumerable<object[]> MarshalSingleCases() + { + yield return new object[] { (float)Math.PI }; + yield return new object[] { 0.0f }; + yield return new object[] { float.MaxValue }; + yield return new object[] { float.MinValue }; + yield return new object[] { float.NegativeInfinity }; + yield return new object[] { float.PositiveInfinity }; + yield return new object[] { float.NaN }; + } + + [Theory] + [MemberData(nameof(MarshalSingleCases))] + public void JsImportSingle(float value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Single, + JavaScriptTestHelper.retrieve1_Single, + JavaScriptTestHelper.echo1_Single, + JavaScriptTestHelper.throw1_Single, + JavaScriptTestHelper.identity1_Single, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalSingleCases))] + public void JsExportSingle(float value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Single, + nameof(JavaScriptTestHelper.EchoSingle), + "number"); + } + #endregion Single + + #region IntPtr + public static IEnumerable<object[]> MarshalIntPtrCases() + { + yield return new object[] { (IntPtr)42 }; + yield return new object[] { IntPtr.Zero }; + yield return new object[] { (IntPtr)1 }; + yield return new object[] { (IntPtr)(-1) }; + yield return new object[] { IntPtr.MaxValue }; + yield return new object[] { IntPtr.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalIntPtrCases))] + public void JsImportIntPtr(IntPtr value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_IntPtr, + JavaScriptTestHelper.retrieve1_IntPtr, + JavaScriptTestHelper.echo1_IntPtr, + JavaScriptTestHelper.throw1_IntPtr, + JavaScriptTestHelper.identity1_IntPtr, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalIntPtrCases))] + public void JsExportIntPtr(IntPtr value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_IntPtr, + nameof(JavaScriptTestHelper.EchoIntPtr), + "number"); + } + #endregion IntPtr + + #region VoidPtr + + [Theory] + [MemberData(nameof(MarshalIntPtrCases))] + public unsafe void JsImportVoidPtr(IntPtr xvalue) + { + void* value = (void*)xvalue; + + JavaScriptTestHelper.store1_VoidPtr(value); + void* res = JavaScriptTestHelper.retrieve1_VoidPtr(); + Assert.True(value == res); + res = JavaScriptTestHelper.echo1_VoidPtr(value); + Assert.True(value == res); + + var actualJsType = JavaScriptTestHelper.getType1(); + Assert.Equal("number", actualJsType); + } + + [Theory] + [MemberData(nameof(MarshalIntPtrCases))] + public unsafe void JsExportVoidPtr(IntPtr xvalue) + { + void* value = (void*)xvalue; + void* res = JavaScriptTestHelper.invoke1_VoidPtr(value, nameof(JavaScriptTestHelper.EchoVoidPtr)); + Assert.True(value == res); + } + #endregion VoidPtr + + #region Datetime + public static IEnumerable<object[]> MarshalDateTimeCases() + { + yield return new object[] { new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; + yield return new object[] { TrimNano(DateTime.UtcNow) }; + yield return new object[] { TrimNano(DateTime.MaxValue) }; + } + + [Theory] + [MemberData(nameof(MarshalDateTimeCases))] + public void JSImportDateTime(DateTime value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_DateTime, + JavaScriptTestHelper.retrieve1_DateTime, + JavaScriptTestHelper.echo1_DateTime, + JavaScriptTestHelper.throw1_DateTime, + JavaScriptTestHelper.identity1_DateTime, + "object", "Date"); + } + + [Theory] + [MemberData(nameof(MarshalDateTimeCases))] + public void JsExportDateTime(DateTime value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_DateTime, + nameof(JavaScriptTestHelper.EchoDateTime), + "object", "Date"); + } + #endregion Datetime + + #region DateTimeOffset + public static IEnumerable<object[]> MarshalDateTimeOffsetCases() + { + yield return new object[] { DateTimeOffset.FromUnixTimeSeconds(0) }; + yield return new object[] { TrimNano(DateTimeOffset.UtcNow) }; + yield return new object[] { TrimNano(DateTimeOffset.MaxValue) }; + } + + [Theory] + [MemberData(nameof(MarshalDateTimeOffsetCases))] + public void JSImportDateTimeOffset(DateTimeOffset value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_DateTimeOffset, + JavaScriptTestHelper.retrieve1_DateTimeOffset, + JavaScriptTestHelper.echo1_DateTimeOffset, + JavaScriptTestHelper.throw1_DateTimeOffset, + JavaScriptTestHelper.identity1_DateTimeOffset, + "object", "Date"); + } + + [Theory] + [MemberData(nameof(MarshalDateTimeOffsetCases))] + public void JsExportDateTimeOffset(DateTimeOffset value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_DateTimeOffset, + nameof(JavaScriptTestHelper.EchoDateTimeOffset), + "object", "Date"); + } + #endregion DateTimeOffset + + #region NullableBoolean + public static IEnumerable<object[]> MarshalNullableBooleanCases() + { + yield return new object[] { null }; + yield return new object[] { true }; + yield return new object[] { false }; + } + + [Theory] + [MemberData(nameof(MarshalNullableBooleanCases))] + public void JsImportNullableBoolean(bool? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableBoolean, + JavaScriptTestHelper.retrieve1_NullableBoolean, + JavaScriptTestHelper.echo1_NullableBoolean, + JavaScriptTestHelper.throw1_NullableBoolean, + JavaScriptTestHelper.identity1_NullableBoolean, + "boolean"); + } + + [Theory] + [MemberData(nameof(MarshalNullableBooleanCases))] + public void JsExportNullableBoolean(bool? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableBoolean, + nameof(JavaScriptTestHelper.EchoNullableBoolean), + "boolean"); + } + #endregion NullableBoolean + + #region NullableInt32 + public static IEnumerable<object[]> MarshalNullableInt32Cases() + { + yield return new object[] { null }; + yield return new object[] { 42 }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { -1 }; + yield return new object[] { int.MaxValue }; + yield return new object[] { int.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalNullableInt32Cases))] + public void JsImportNullableInt32(int? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableInt32, + JavaScriptTestHelper.retrieve1_NullableInt32, + JavaScriptTestHelper.echo1_NullableInt32, + JavaScriptTestHelper.throw1_NullableInt32, + JavaScriptTestHelper.identity1_NullableInt32, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalNullableInt32Cases))] + public void JsExportNullableInt32(int? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableInt32, + nameof(JavaScriptTestHelper.EchoNullableInt32), + "number"); + } + #endregion NullableInt32 + + #region NullableBigInt64 + public static IEnumerable<object[]> MarshalNullableBigInt64Cases() + { + yield return new object[] { null }; + yield return new object[] { 42L }; + yield return new object[] { 0L }; + yield return new object[] { 1L }; + yield return new object[] { -1L }; + yield return new object[] { MAX_SAFE_INTEGER }; + yield return new object[] { MIN_SAFE_INTEGER }; + yield return new object[] { long.MaxValue }; + yield return new object[] { long.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalNullableBigInt64Cases))] + public void JsImportNullableBigInt64(long? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableBigInt64, + JavaScriptTestHelper.retrieve1_NullableBigInt64, + JavaScriptTestHelper.echo1_NullableBigInt64, + JavaScriptTestHelper.throw1_NullableBigInt64, + JavaScriptTestHelper.identity1_NullableBigInt64, + "bigint"); + } + + [Theory] + [MemberData(nameof(MarshalNullableBigInt64Cases))] + public void JsExportNullableBigInt64(long? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableBigInt64, + nameof(JavaScriptTestHelper.EchoNullableBigInt64), + "bigint"); + } + #endregion NullableBigInt64 + + #region NullableIntPtr + public static IEnumerable<object[]> MarshalNullableIntPtrCases() + { + yield return new object[] { null }; + yield return new object[] { (IntPtr)42 }; + yield return new object[] { IntPtr.Zero }; + yield return new object[] { (IntPtr)1 }; + yield return new object[] { (IntPtr)(-1) }; + yield return new object[] { IntPtr.MaxValue }; + yield return new object[] { IntPtr.MinValue }; + } + + [Theory] + [MemberData(nameof(MarshalNullableIntPtrCases))] + public void JsImportNullableIntPtr(IntPtr? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableIntPtr, + JavaScriptTestHelper.retrieve1_NullableIntPtr, + JavaScriptTestHelper.echo1_NullableIntPtr, + JavaScriptTestHelper.throw1_NullableIntPtr, + JavaScriptTestHelper.identity1_NullableIntPtr, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalNullableIntPtrCases))] + public void JsExportNullableIntPtr(IntPtr? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableIntPtr, + nameof(JavaScriptTestHelper.EchoNullableIntPtr), + "number"); + } + #endregion NullableIntPtr + + #region NullableDouble + public static IEnumerable<object[]> MarshalNullableDoubleCases() + { + yield return new object[] { null }; + yield return new object[] { Math.PI }; + yield return new object[] { 0.0 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.MinValue }; + yield return new object[] { double.NegativeInfinity }; + yield return new object[] { double.PositiveInfinity }; + yield return new object[] { double.NaN }; + } + + [Theory] + [MemberData(nameof(MarshalNullableDoubleCases))] + public void JsImportNullableDouble(double? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableDouble, + JavaScriptTestHelper.retrieve1_NullableDouble, + JavaScriptTestHelper.echo1_NullableDouble, + JavaScriptTestHelper.throw1_NullableDouble, + JavaScriptTestHelper.identity1_NullableDouble, + "number"); + } + + [Theory] + [MemberData(nameof(MarshalNullableDoubleCases))] + public void JsExportNullableDouble(double? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableDouble, + nameof(JavaScriptTestHelper.EchoNullableDouble), + "number"); + } + #endregion NullableDouble + + #region NullableDateTime + public static IEnumerable<object[]> MarshalNullableDateTimeCases() + { + yield return new object[] { null }; + yield return new object[] { new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; + yield return new object[] { TrimNano(DateTime.UtcNow) }; + yield return new object[] { TrimNano(DateTime.MaxValue) }; + } + + [Theory] + [MemberData(nameof(MarshalNullableDateTimeCases))] + public void JsImportNullableDateTime(DateTime? value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_NullableDateTime, + JavaScriptTestHelper.retrieve1_NullableDateTime, + JavaScriptTestHelper.echo1_NullableDateTime, + JavaScriptTestHelper.throw1_NullableDateTime, + JavaScriptTestHelper.identity1_NullableDateTime, + "object"); + } + + [Theory] + [MemberData(nameof(MarshalNullableDateTimeCases))] + public void JsExportNullableDateTime(DateTime? value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_NullableDateTime, + nameof(JavaScriptTestHelper.EchoNullableDateTime), + "object"); + } + #endregion NullableDateTime + + #region String + public static IEnumerable<object[]> MarshalStringCases() + { + yield return new object[] { null }; + yield return new object[] { string.Empty }; + yield return new object[] { "Ahoj" + Random.Shared.Next() };// shorted than 256 -> check in JS interned + yield return new object[] { "Ahoj" + new string('!', 300) };// longer than 256 -> no check in JS interned + yield return new object[] { string.Intern("dotnet") }; + } + + [Theory] + [MemberData(nameof(MarshalStringCases))] + public void JsImportString(string value) + { + JsImportTest(value, + JavaScriptTestHelper.store1_String, + JavaScriptTestHelper.retrieve1_String, + JavaScriptTestHelper.echo1_String, + JavaScriptTestHelper.throw1_String, + JavaScriptTestHelper.identity1_String + , "string"); + } + + [Theory] + [MemberData(nameof(MarshalStringCases))] + public void JsExportString(string value) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_String, + nameof(JavaScriptTestHelper.EchoString), + "string"); + } + + #endregion String + + #region Object + public static IEnumerable<object[]> MarshalObjectCases() + { + yield return new object[] { new object(), "ManagedObject" }; + yield return new object[] { null, null }; + } + + [Theory] + [MemberData(nameof(MarshalObjectCases))] + public void JSImportObject(object value, string clazz) + { + JsImportTest(value, + JavaScriptTestHelper.store1_Object, + JavaScriptTestHelper.retrieve1_Object, + JavaScriptTestHelper.echo1_Object, + JavaScriptTestHelper.throw1_Object, + JavaScriptTestHelper.identity1_Object, + "object", clazz); + } + + [Theory] + [MemberData(nameof(MarshalObjectCases))] + public void JsExportObject(object value, string clazz) + { + JsExportTest(value, + JavaScriptTestHelper.invoke1_Object, + nameof(JavaScriptTestHelper.EchoObject), + "object", clazz); + } + #endregion Object + + #region Exception + public static IEnumerable<object[]> MarshalExceptionCases() + { + yield return new object[] { new Exception("Test"), "ManagedError" }; + yield return new object[] { null, "JSTestError" }; + yield return new object[] { null, null }; + } + + [Theory] + [MemberData(nameof(MarshalExceptionCases))] + public void JSImportException(Exception value, string clazz) + { + if (clazz == "JSTestError") + { + value = JavaScriptTestHelper.createException("!CreateEx!"); + } + + JsImportTest(value, + JavaScriptTestHelper.store1_Exception, + JavaScriptTestHelper.retrieve1_Exception, + JavaScriptTestHelper.echo1_Exception, + JavaScriptTestHelper.throw1_Exception, + JavaScriptTestHelper.identity1_Exception, + "object", clazz); + } + + [Theory] + [MemberData(nameof(MarshalExceptionCases))] + public void JsExportException(Exception value, string clazz) + { + if (clazz == "JSTestError") + { + value = JavaScriptTestHelper.createException("!CreateEx!"); + } + + JsExportTest(value, + JavaScriptTestHelper.invoke1_Exception, + nameof(JavaScriptTestHelper.EchoException), + "object", clazz); + } + #endregion Exception + + #region JSObject + public static IEnumerable<object[]> MarshalIJSObjectCases() + { + yield return new object[] { null, "JSData" }; + yield return new object[] { null, null }; + } + + [Theory] + [MemberData(nameof(MarshalIJSObjectCases))] + public void JSImportIJSObject(JSObject value, string clazz) + { + if (clazz == "JSData") + { + value = JavaScriptTestHelper.createData("!CreateJS!"); + } + + JsImportTest(value, + JavaScriptTestHelper.store1_JSObject, + JavaScriptTestHelper.retrieve1_JSObject, + JavaScriptTestHelper.echo1_JSObject, + JavaScriptTestHelper.throw1_JSObject, + JavaScriptTestHelper.identity1_JSObject, + "object", clazz); + } + + [Theory] + [MemberData(nameof(MarshalIJSObjectCases))] + public void JsExportIJSObject(JSObject value, string clazz) + { + if (clazz == "JSData") + { + value = JavaScriptTestHelper.createData("!CreateJS!"); + } + + JsExportTest(value, + JavaScriptTestHelper.invoke1_JSObject, + nameof(JavaScriptTestHelper.EchoIJSObject), + "object", clazz); + } + #endregion JSObject + + #region ProxyOfProxy + [Fact] + public void ProxyOfProxyThrows() + { + // proxy of proxy should throw + JavaScriptTestHelper.store1_Object(new object()); + Assert.Throws<JSException>(() => JavaScriptTestHelper.retrieve1_JSObject()); + } + + + [Fact] + public void ProxyOfIntThrows() + { + // JSObject proxy of int should throw + JavaScriptTestHelper.store1_Int32(13); + Assert.Throws<JSException>(() => JavaScriptTestHelper.retrieve1_JSObject()); + } + #endregion + + #region Task + + [Fact] + public async Task JsImportSleep() + { + await JavaScriptTestHelper.sleep(100); + } + + [Fact] + public async Task JsImportThenVoid() + { + TaskCompletionSource tcs = new TaskCompletionSource(); + JavaScriptTestHelper.thenvoid(tcs.Task); + GC.Collect(); + tcs.SetResult(); + + GC.Collect(); + + await Task.Yield(); + } + + [Fact] + [OuterLoop] + public async Task JsImportForeverMany() + { + for (int i = 0; i < 1000; i++) + { + if (i % 100 == 0) + { + GC.Collect(); + await Task.Yield(); + } + var forever = JavaScriptTestHelper.forever(); + Assert.False(forever.IsCompleted); + } + } + + [Fact] + public async Task JsImportVoidTaskPending() + { + GC.Collect(); + var pending = Task.Delay(1000); + var res = JavaScriptTestHelper.await2(pending); + GC.Collect(); + Assert.False(res.IsCompleted); + await Task.Yield(); + GC.Collect(); + await res; + GC.Collect(); + Assert.True(res.IsCompleted); + GC.Collect(); + } + + [Fact] + public async Task JsImportVoidTaskComplete() + { + GC.Collect(); + var resComplete = JavaScriptTestHelper.await2(Task.CompletedTask); + GC.Collect(); + await Task.Delay(100); + GC.Collect(); + await Task.Yield(); + GC.Collect(); + Assert.True(resComplete.IsCompleted); + GC.Collect(); + await resComplete; + GC.Collect(); + } + + [Fact] + public async Task JsImportSleep2() + { + int ms = await JavaScriptTestHelper.sleep_Int(100); + Assert.Equal(100, ms); + } + + + [Fact] + public async Task JsImportTaskEchoComplete() + { + var task = JavaScriptTestHelper.echo1_Task(Task.CompletedTask); + Assert.NotEqual(Task.CompletedTask, task); + // yield to main loop, because "the respective handler function will be called asynchronously" + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#return_value + await Task.Delay(100); + Assert.True(task.IsCompleted); + } + + [Fact] + public async Task JsImportTaskEchoPendingResult() + { + TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); + var task = JavaScriptTestHelper.echo1_Task(tcs.Task); + Assert.NotEqual(tcs.Task, task); + Assert.False(task.IsCompleted); + + tcs.SetResult("test"); + // yield to main loop, because "the respective handler function will be called asynchronously" + await Task.Delay(100); + Assert.True(task.IsCompleted); + Assert.Equal(typeof(Task), task.GetType()); + } + + [Fact] + public async Task JsImportTaskEchoPendingException() + { + TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); + var task = JavaScriptTestHelper.echo1_Task(tcs.Task); + Assert.NotEqual(tcs.Task, task); + Assert.False(task.IsCompleted); + + tcs.SetException(new Exception("Test")); + // yield to main loop, because "the respective handler function will be called asynchronously" + await Task.Delay(100); + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync<Exception>(async () => await task); + } + + public static IEnumerable<object[]> TaskCases() + { + yield return new object[] { Math.PI }; + yield return new object[] { 0 }; + yield return new object[] { "test" }; + yield return new object[] { null }; + } + + [Theory] + [MemberData(nameof(TaskCases))] + public async Task JsImportTaskAwaitPendingResult(object result) + { + TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); + var task = JavaScriptTestHelper.await1(tcs.Task); + Assert.NotEqual(tcs.Task, task); + Assert.False(task.IsCompleted); + + tcs.SetResult(result); + // yield to main loop, because "the respective handler function will be called asynchronously" + await Task.Delay(100); + Assert.True(task.IsCompleted); + var res = await task; + if (result != null && result.GetType() == typeof(int)) + { + Assert.Equal(result, Convert.ToInt32(res)); + } + else + { + Assert.Equal(result, res); + } + } + + [Fact] + public async Task JsImportTaskAwaitPendingException() + { + TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); + var task = JavaScriptTestHelper.await1(tcs.Task); + Assert.NotEqual(tcs.Task, task); + Assert.False(task.IsCompleted); + + tcs.SetException(new Exception("Test")); + // yield to main loop, because "the respective handler function will be called asynchronously" + await Task.Delay(100); + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync<Exception>(async () => await task); + } + + [Fact] + public async Task JsImportTaskAwaitPendingExceptionValue() + { + TaskCompletionSource<Exception> tcs = new TaskCompletionSource<Exception>(); + var task = JavaScriptTestHelper.await1_TaskOfException(tcs.Task); + Assert.NotEqual(tcs.Task, task); + Assert.False(task.IsCompleted); + + tcs.SetResult(new Exception("Test")); + // yield to main loop, because "the respective handler function will be called asynchronously" + await Task.Delay(100); + Assert.True(task.IsCompletedSuccessfully); + Assert.Equal("Test", task.Result.Message); + } + + + [Fact] + public async Task JsImportTaskAwait() + { + var task = JavaScriptTestHelper.awaitvoid(Task.CompletedTask); + await Task.Delay(100); + Assert.True(task.IsCompleted); + await task; + } + + [Theory] + [MemberData(nameof(MarshalInt32Cases))] + public async Task JsExportTaskOfInt(int value) + { + TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); + + var res = JavaScriptTestHelper.invoke1_TaskOfInt(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfObject)); + tcs.SetResult(value); + await Task.Yield(); + var rr = await res; + await Task.Yield(); + Assert.Equal(value, rr); + //GC.Collect(); + } + + #endregion + + #region Action + + [Fact] + public void JsImportCallback_EchoAction() + { + bool called = false; + Action expected = () => + { + called = true; + }; + var actual = JavaScriptTestHelper.echo1_ActionAction(expected); + Assert.NotEqual(expected, actual); + Assert.False(called); + actual(); + Assert.True(called); + } + + + [Fact] + [OuterLoop] + public async Task JsImportCallback_EchoActionMany() + { + int a = 1; + for (int i = 0; i < 1000; i++) + { + Action expected = () => + { + a += i; + }; + var actual = JavaScriptTestHelper.echo1large_ActionAction(expected); + Assert.NotEqual(expected, actual); + if (i % 100 == 0) + { + await Task.Yield(); + GC.Collect(); + } + } + } + + [Fact] + public void JsImportCallback_Action() + { + bool called = false; + JavaScriptTestHelper.back3_Action(() => + { + called = true; + }); + Assert.True(called); + } + + [Fact] + public void JsImportEcho_ActionAction() + { + bool called = false; + Action res = JavaScriptTestHelper.echo1_ActionAction(() => + { + called = true; + }); + Assert.False(called); + res.Invoke(); + Assert.True(called); + } + + [Fact] + public void JsImportEcho_ActionIntActionInt() + { + int calledA = -1; + Action<int> res = JavaScriptTestHelper.echo1_ActionIntActionInt((a) => + { + calledA = a; + }); + Assert.Equal(-1, calledA); + res.Invoke(42); + Assert.Equal(42, calledA); + } + + [Fact] + public void JsImportCallback_ActionInt() + { + int called = -1; + JavaScriptTestHelper.back3_ActionInt((a) => + { + called = a; + }, 42); + Assert.Equal(42, called); + } + + [Fact] + public void JsImportCallback_FunctionIntInt() + { + int called = -1; + int res = JavaScriptTestHelper.back3_FunctionIntInt((a) => + { + called = a; + return a; + }, 42); + Assert.Equal(42, called); + Assert.Equal(42, res); + } + + [Fact] + public void JsImportBackCallback_FunctionIntInt() + { + int called = -1; + Func<int, int> res = JavaScriptTestHelper.backback_FuncIntFuncInt((a) => + { + called = a; + return a; + }, 42); + Assert.Equal(-1, called); + int actual = res.Invoke(42); + Assert.Equal(84, actual); + Assert.Equal(84, called); + } + + [Fact] + public void JsImportBackCallback_FunctionIntIntIntInt() + { + int calledA = -1; + int calledB = -1; + Func<int, int, int> res = JavaScriptTestHelper.backback_FuncIntIntFuncIntInt((a, b) => + { + calledA = a; + calledB = b; + return a + b; + }, 42, 43); + Assert.Equal(-1, calledA); + Assert.Equal(-1, calledB); + int actual = res.Invoke(40, 41); + Assert.Equal(166, actual); + Assert.Equal(82, calledA); + Assert.Equal(84, calledB); + } + + [Fact] + public void JsImportCallback_ActionIntInt() + { + int calledA = -1; + int calledB = -1; + JavaScriptTestHelper.back3_ActionIntInt((a, b) => + { + calledA = a; + calledB = b; + }, 42, 43); + Assert.Equal(42, calledA); + Assert.Equal(43, calledB); + } + + [Fact] + public void JsImportCallback_ActionLongLong() + { + long calledA = -1; + long calledB = -1; + JavaScriptTestHelper.back3_ActionLongLong((a, b) => + { + calledA = a; + calledB = b; + }, 42, 43); + Assert.Equal(42, calledA); + Assert.Equal(43, calledB); + } + + [Fact] + public void JsImportCallback_ActionIntLong() + { + int calledA = -1; + long calledB = -1; + JavaScriptTestHelper.back3_ActionIntLong((a, b) => + { + calledA = a; + calledB = b; + }, 42, 43); + Assert.Equal(42, calledA); + Assert.Equal(43, calledB); + } + + [Fact] + public void JsImportCallback_ActionIntThrow() + { + int called = -1; + Exception expected = new Exception("test!!"); + Exception actual = Assert.Throws<Exception>(() => JavaScriptTestHelper.back3_ActionInt((a) => + { + called = a; + throw expected; + }, 42)); + Assert.Equal(42, called); + Assert.Same(expected, actual); + } + + [Fact] + public void JsExportCallback_FunctionIntInt() + { + int called = -1; + var chain = JavaScriptTestHelper.invoke1_FuncOfIntInt((int a) => + { + called = a; + return a; + }, nameof(JavaScriptTestHelper.BackFuncOfIntInt)); + + Assert.Equal(-1, called); + var actual = chain(42); + Assert.Equal(42, actual); + Assert.Equal(42, called); + } + + [Fact] + public void JsExportCallback_FunctionIntIntThrow() + { + int called = -1; + var expected = new Exception("test!!"); + var chain = JavaScriptTestHelper.invoke1_FuncOfIntInt((int a) => + { + called = a; + throw expected; + }, nameof(JavaScriptTestHelper.BackFuncOfIntInt)); + + Assert.Equal(-1, called); + var actual = Assert.Throws<Exception>(() => chain(42)); + Assert.Equal(42, called); + Assert.Same(expected, actual); + } + + [Fact] + public void JsImportMath() + { + Func<int, int, int> plus = Utils.CreateFunctionIntIntInt("a", "b", @"return a+b"); + Assert.Equal(3, plus(1, 2)); + } + + #endregion + + private void JsExportTest<T>(T value + , Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null) + { + T res; + res = invoke(value, echoName); + Assert.Equal(value, res); + } + + private void JsImportTest<T>(T value + , Action<T> store1 + , Func<T> retrieve1 + , Func<T, T> echo1 + , Func<T, T> throw1 + , Func<T, bool> identity1 + , string jsType, string? jsClass = null) + { + if (value == null) + { + jsClass = null; + jsType = "object"; + } + + // invoke + store1(value); + var res = retrieve1(); + Assert.Equal(value, res); + res = echo1(value); + Assert.Equal(value, res); + var equals = identity1(value); + Assert.True(equals, "value not equals"); + + var actualJsType = JavaScriptTestHelper.getType1(); + Assert.Equal(jsType, actualJsType); + + if (jsClass != null) + { + var actualJsClass = JavaScriptTestHelper.getClass1(); + Assert.Equal(jsClass, actualJsClass); + } + var exThrow0 = Assert.Throws<JSException>(() => JavaScriptTestHelper.throw0()); + Assert.Contains("throw-0-msg", exThrow0.Message); + Assert.DoesNotContain(" at ", exThrow0.Message); + Assert.Contains(" at throw0", exThrow0.StackTrace); + + var exThrow1 = Assert.Throws<JSException>(() => throw1(value)); + Assert.Contains("throw1-msg", exThrow1.Message); + Assert.DoesNotContain(" at ", exThrow1.Message); + Assert.Contains(" at throw1", exThrow1.StackTrace); + + // anything is a system.object, sometimes it would be JSObject wrapper + if (typeof(T).IsPrimitive) + { + if (typeof(T) != typeof(long)) + { + + object resBoxed = JavaScriptTestHelper.echo1_Object(value); + // js Number always boxes as double + if (typeof(T) == typeof(IntPtr)) + { + //TODO Assert.Equal((IntPtr)(object)value, (IntPtr)(int)(double)resBoxed); + } + else if (typeof(T) == typeof(bool)) + { + Assert.Equal((bool)(object)value, (bool)resBoxed); + } + else if (typeof(T) == typeof(char)) + { + Assert.Equal((char)(object)value, (char)(double)resBoxed); + } + else + { + Assert.Equal(Convert.ToDouble(value), resBoxed); + } + } + + //TODO var task = JavaScriptTestHelper.await1(Task.FromResult((object)value)); + } + else if (typeof(T) == typeof(DateTime)) + { + var resBoxed = JavaScriptTestHelper.echo1_Object(value); + Assert.Equal(value, resBoxed); + } + else if (typeof(T) == typeof(DateTimeOffset)) + { + var resBoxed = JavaScriptTestHelper.echo1_Object(value); + Assert.Equal(((DateTimeOffset)(object)value).UtcDateTime, resBoxed); + } + else if (Nullable.GetUnderlyingType(typeof(T)) != null) + { + var vt = Nullable.GetUnderlyingType(typeof(T)); + if (vt != typeof(long)) + { + var resBoxed = JavaScriptTestHelper.echo1_Object(value); + if (resBoxed != null) + { + if (vt == typeof(bool)) + { + Assert.Equal(((bool?)(object)value).Value, (bool)resBoxed); + } + else if (vt == typeof(char)) + { + Assert.Equal(((char?)(object)value).Value, (char)resBoxed); + } + else if (vt == typeof(DateTime)) + { + Assert.Equal(((DateTime?)(object)value).Value, resBoxed); + } + else if (vt == typeof(DateTimeOffset)) + { + Assert.Equal(((DateTimeOffset?)(object)value).Value.UtcDateTime, resBoxed); + } + else if (vt == typeof(IntPtr)) + { + // TODO Assert.Equal((double)((IntPtr?)(object)value).Value, resBoxed); + } + else + { + Assert.Equal(Convert.ToDouble(value), resBoxed); + } + } + else + { + Assert.Equal(value, default(T)); + } + } + } + else + { + var resObj = JavaScriptTestHelper.retrieve1_Object(); + if (resObj == null || resObj.GetType() != typeof(JSObject)) + { + Assert.Equal(value, resObj); + } + } + + if (typeof(Exception).IsAssignableFrom(typeof(T))) + { + // all exceptions are Exception + var resEx = JavaScriptTestHelper.retrieve1_Exception(); + Assert.Equal((Exception)(object)value, resEx); + } + } + + public async Task InitializeAsync() + { + await JavaScriptTestHelper.InitializeAsync(); + } + + public Task DisposeAsync() => Task.CompletedTask; + + // js Date doesn't have nanosecond precision + public static DateTime TrimNano(DateTime date) + { + return new DateTime(date.Ticks - (date.Ticks % TimeSpan.TicksPerMillisecond), DateTimeKind.Utc); + } + + public static DateTimeOffset TrimNano(DateTimeOffset date) + { + return new DateTime(date.Ticks - (date.Ticks % TimeSpan.TicksPerMillisecond), DateTimeKind.Utc); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs new file mode 100644 index 00000000000..61d92f193ab --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -0,0 +1,915 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.Marshalling; +using System.Threading.Tasks; +using System.Xml.Serialization; + +[assembly: DisableRuntimeMarshalling] + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public partial class JavaScriptTestHelper + { + [JSImport("globalThis.console.log")] + public static partial void Log([JSMarshalAs<JSType.String>] string message); + + [JSExport] + [return: JSMarshalAs<JSType.Discard>] + public static void ConsoleWriteLine([JSMarshalAs<JSType.String>] string message) + { + Console.WriteLine(message); + } + + [JSExport] + [return: JSMarshalAs<JSType.Date>] + public static DateTime Now() + { + return DateTime.Now; + } + + [JSImport("create_function", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] + public static partial Func<int, int, int> createMath([JSMarshalAs<JSType.String>] string a, [JSMarshalAs<JSType.String>] string b, [JSMarshalAs<JSType.String>] string code); + + [JSImport("getType1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string getType1(); + + [JSImport("getClass1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string getClass1(); + + [JSImport("throw0", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Discard>] + internal static partial void throw0(); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Void>>] + internal static partial Task echo1_Task([JSMarshalAs<JSType.Promise<JSType.Void>>] Task arg1); + + [JSImport("createException", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Error>] + internal static partial Exception createException([JSMarshalAs<JSType.String>] string name); + + [JSImport("createData", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject createData([JSMarshalAs<JSType.String>] string name); + + #region relaxed + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial void Relaxed(string a1, Exception ex, + bool ab, double a6, byte a2, char a3, short a4, float a5, IntPtr a7, + bool? nab, double? na6, byte? na2, char? na3, short? na4, float? na5, IntPtr? na7, + Task<string> ta1, Task<Exception> tex, + Task<bool> tab, Task<double> ta6, Task<byte> ta2, Task<char> ta3, Task<short> ta4, Task<float> ta5, Task<IntPtr> ta7, + string[] aa1, byte[] aab, double[] aad, int[] aai + ); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial double RelaxedDouble(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial string RelaxedString(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial string[] RelaxedStringArray(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Exception RelaxedException(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial bool RelaxedBool(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial double? RelaxedNullableDouble(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial bool? RelaxedNullableBool(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Task RelaxedTask(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Task<double> RelaxedTaskDouble(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Task<string> RelaxedTaskString(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Task<Exception> RelaxedTaskException(); + + [JSImport("dummy", "JavaScriptTestHelper")] + internal static partial Task<bool> RelaxedTaskBool(); + + + #endregion + + #region Arrays + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.Number>>] + internal static partial byte[]? echo1_ByteArray([JSMarshalAs<JSType.Array<JSType.Number>>] byte[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial byte? store_ByteArray([JSMarshalAs<JSType.Array<JSType.Number>>] byte[]? value, [JSMarshalAs<JSType.Number>] int index); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.Number>>] + internal static partial int[]? echo1_Int32Array([JSMarshalAs<JSType.Array<JSType.Number>>] int[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int? store_Int32Array([JSMarshalAs<JSType.Array<JSType.Number>>] int[]? value, [JSMarshalAs<JSType.Number>] int index); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.Number>>] + internal static partial double[]? echo1_DoubleArray([JSMarshalAs<JSType.Array<JSType.Number>>] double[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double? store_DoubleArray([JSMarshalAs<JSType.Array<JSType.Number>>] double[]? value, [JSMarshalAs<JSType.Number>] int index); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.String>>] + internal static partial string[]? echo1_StringArray([JSMarshalAs<JSType.Array<JSType.String>>] string[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string? store_StringArray([JSMarshalAs<JSType.Array<JSType.String>>] string[]? value, [JSMarshalAs<JSType.Number>] int index); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.Any>>] + internal static partial object[]? echo1_ObjectArray([JSMarshalAs<JSType.Array<JSType.Any>>] object[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Any>] + internal static partial object? store_ObjectArray([JSMarshalAs<JSType.Array<JSType.Any>>] object[]? value, [JSMarshalAs<JSType.Number>] int index); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Array<JSType.Object>>] + internal static partial JSObject[]? echo1_JSObjectArray([JSMarshalAs<JSType.Array<JSType.Object>>] JSObject[]? value); + + [JSImport("storeAt", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject? store_JSObjectArray([JSMarshalAs<JSType.Array<JSType.Object>>] JSObject[]? value, [JSMarshalAs<JSType.Number>] int index); + + #endregion + + #region Views + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial Span<byte> echo1_SpanOfByte([JSMarshalAs<JSType.MemoryView>] Span<byte> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial Span<int> echo1_SpanOfInt32([JSMarshalAs<JSType.MemoryView>] Span<int> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial Span<double> echo1_SpanOfDouble([JSMarshalAs<JSType.MemoryView>] Span<double> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial ArraySegment<byte> echo1_ArraySegmentOfByte([JSMarshalAs<JSType.MemoryView>] ArraySegment<byte> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial ArraySegment<int> echo1_ArraySegmentOfInt32([JSMarshalAs<JSType.MemoryView>] ArraySegment<int> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + [JSImport("echo1view", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.MemoryView>] + internal static partial ArraySegment<double> echo1_ArraySegmentOfDouble([JSMarshalAs<JSType.MemoryView>] ArraySegment<double> value, [JSMarshalAs<JSType.Boolean>] bool edit); + + #endregion + + #region Int32 + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int echo1_Int32([JSMarshalAs<JSType.Number>] int value); + [JSImport("store1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Void>] + internal static partial void store1_Int32([JSMarshalAs<JSType.Number>] int value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int retrieve1_Int32(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Int32([JSMarshalAs<JSType.Number>] int value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int throw1_Int32([JSMarshalAs<JSType.Number>] int value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int invoke1_Int32([JSMarshalAs<JSType.Number>] int value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static int EchoInt32([JSMarshalAs<JSType.Number>] int arg1) + { + return arg1; + } + #endregion Int32 + + #region String + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string echo1_String([JSMarshalAs<JSType.String>] string value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_String([JSMarshalAs<JSType.String>] string value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string retrieve1_String(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_String([JSMarshalAs<JSType.String>] string value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string throw1_String([JSMarshalAs<JSType.String>] string value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial string invoke1_String([JSMarshalAs<JSType.String>] string value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.String>] + public static string EchoString([JSMarshalAs<JSType.String>] string arg1) + { + return arg1; + } + #endregion String + + #region Object + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Any>] + internal static partial object echo1_Object([JSMarshalAs<JSType.Any>] object value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Object([JSMarshalAs<JSType.Any>] object value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Any>] + internal static partial object retrieve1_Object(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Object([JSMarshalAs<JSType.Any>] object value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Any>] + internal static partial object throw1_Object([JSMarshalAs<JSType.Any>] object value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Any>] + internal static partial object invoke1_Object([JSMarshalAs<JSType.Any>] object value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Any>] + public static object EchoObject([JSMarshalAs<JSType.Any>] object arg1) + { + return arg1; + } + #endregion Object + + #region Exception + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Error>] + internal static partial Exception echo1_Exception([JSMarshalAs<JSType.Error>] Exception value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Exception([JSMarshalAs<JSType.Error>] Exception value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Error>] + internal static partial Exception retrieve1_Exception(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Exception([JSMarshalAs<JSType.Error>] Exception value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Error>] + internal static partial Exception throw1_Exception([JSMarshalAs<JSType.Error>] Exception value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Error>] + internal static partial Exception invoke1_Exception([JSMarshalAs<JSType.Error>] Exception value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Error>] + public static Exception EchoException([JSMarshalAs<JSType.Error>] Exception arg1) + { + return arg1; + } + #endregion Exception + + #region Task + [JSImport("awaitvoid", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Void>>] + internal static partial Task awaitvoid([JSMarshalAs<JSType.Promise<JSType.Void>>] Task arg1); + [JSImport("sleep", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Void>>] + internal static partial Task sleep([JSMarshalAs<JSType.Number>] int ms); + [JSImport("forever", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Void>>] + internal static partial Task forever(); + [JSImport("sleep", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Number>>] + internal static partial Task<int> sleep_Int([JSMarshalAs<JSType.Number>] int ms); + + [JSImport("sleep", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Number>>] + internal static partial Task<int>? sleepMaybe_Int([JSMarshalAs<JSType.Number>] int ms); + + [JSImport("await2", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Void>>] + internal static partial Task await2([JSMarshalAs<JSType.Promise<JSType.Void>>] Task arg1); + + [JSImport("thenvoid", "JavaScriptTestHelper")] + internal static partial void thenvoid([JSMarshalAs<JSType.Promise<JSType.Void>>] Task arg1); + + [JSImport("await1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Any>>] + internal static partial Task<object> await1([JSMarshalAs<JSType.Promise<JSType.Any>>] Task<object> arg1); + [JSImport("await1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Error>>] + internal static partial Task<Exception> await1_TaskOfException([JSMarshalAs<JSType.Promise<JSType.Error>>] Task<Exception> arg1); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Any>>] + internal static partial Task<object> invoke1_TaskOfObject([JSMarshalAs<JSType.Promise<JSType.Any>>] Task<object> value, [JSMarshalAs<JSType.String>] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Promise<JSType.Number>>] + internal static partial Task<int> invoke1_TaskOfInt([JSMarshalAs<JSType.Promise<JSType.Number>>] Task<int> value, [JSMarshalAs<JSType.String>] string name); + + [JSExport] + [return: JSMarshalAs<JSType.Promise<JSType.Any>>] + public static async Task<object> AwaitTaskOfObject([JSMarshalAs<JSType.Promise<JSType.Any>>] Task<object> arg1) + { + var res = await arg1; + return res; + } + + #endregion + + #region Action + Func + + [JSImport("back3", "JavaScriptTestHelper")] + internal static partial void back3_Action([JSMarshalAs<JSType.Function>] Action action); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function>] + internal static partial Action echo1_ActionAction([JSMarshalAs<JSType.Function>] Action action); + + [JSImport("echo1large", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function>] + internal static partial Action echo1large_ActionAction([JSMarshalAs<JSType.Function>] Action action); + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + internal static partial Action<int> echo1_ActionIntActionInt([JSMarshalAs<JSType.Function<JSType.Number>>] Action<int> action); + + [JSImport("backback", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + internal static partial Func<int, int> backback_FuncIntFuncInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Func<int, int> fun, [JSMarshalAs<JSType.Number>] int a); + + [JSImport("backback", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] + internal static partial Func<int, int, int> backback_FuncIntIntFuncIntInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] Func<int, int, int> fun, [JSMarshalAs<JSType.Number>] int a, [JSMarshalAs<JSType.Number>] int b); + + [JSImport("back3", "JavaScriptTestHelper")] + internal static partial void back3_ActionInt([JSMarshalAs<JSType.Function<JSType.Number>>] Action<int>? action, [JSMarshalAs<JSType.Number>] int a); + + [JSImport("back3", "JavaScriptTestHelper")] + internal static partial void back3_ActionIntInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Action<int, int>? action, [JSMarshalAs<JSType.Number>] int a, [JSMarshalAs<JSType.Number>] int b); + + [JSImport("back3", "JavaScriptTestHelper")] + internal static partial void back3_ActionLongLong([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Action<long, long>? action, [JSMarshalAs<JSType.Number>] long a, [JSMarshalAs<JSType.Number>] long b); + + [JSImport("back3", "JavaScriptTestHelper")] + internal static partial void back3_ActionIntLong([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Action<int, long>? action, [JSMarshalAs<JSType.Number>] int a, [JSMarshalAs<JSType.Number>] long b); + + [JSImport("back3", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int back3_FunctionIntInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Func<int, int>? fun, [JSMarshalAs<JSType.Number>] int a); + + + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + internal static partial Func<int, int> invoke1_FuncOfIntInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Func<int, int> value, [JSMarshalAs<JSType.String>] string name); + + [JSExport] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static Func<int, int> BackFuncOfIntInt([JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] Func<int, int> arg1) + { + return (int a) => + { + return arg1(a); + }; + } + + #endregion + + #region Boolean + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool echo1_Boolean([JSMarshalAs<JSType.Boolean>] bool value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Boolean([JSMarshalAs<JSType.Boolean>] bool value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool retrieve1_Boolean(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Boolean([JSMarshalAs<JSType.Boolean>] bool value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool throw1_Boolean([JSMarshalAs<JSType.Boolean>] bool value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool invoke1_Boolean([JSMarshalAs<JSType.Boolean>] bool value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Boolean>] + public static bool EchoBoolean([JSMarshalAs<JSType.Boolean>] bool arg1) + { + return arg1; + } + #endregion Boolean + + #region Char + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial char echo1_Char([JSMarshalAs<JSType.String>] char value); + [JSImport("store1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Discard>] + internal static partial void store1_Char([JSMarshalAs<JSType.String>] char value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial char retrieve1_Char(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Char([JSMarshalAs<JSType.String>] char value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial char throw1_Char([JSMarshalAs<JSType.String>] char value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.String>] + internal static partial char invoke1_Char([JSMarshalAs<JSType.String>] char value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.String>] + public static char EchoChar([JSMarshalAs<JSType.String>] char arg1) + { + return arg1; + } + #endregion Byte + + #region Byte + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial byte echo1_Byte([JSMarshalAs<JSType.Number>] byte value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Byte([JSMarshalAs<JSType.Number>] byte value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial byte retrieve1_Byte(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Byte([JSMarshalAs<JSType.Number>] byte value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial byte throw1_Byte([JSMarshalAs<JSType.Number>] byte value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial byte invoke1_Byte([JSMarshalAs<JSType.Number>] byte value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static byte EchoByte([JSMarshalAs<JSType.Number>] byte arg1) + { + return arg1; + } + #endregion Byte + + #region Int16 + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial short echo1_Int16([JSMarshalAs<JSType.Number>] short value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Int16([JSMarshalAs<JSType.Number>] short value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial short retrieve1_Int16(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Int16([JSMarshalAs<JSType.Number>] short value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial short throw1_Int16([JSMarshalAs<JSType.Number>] short value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial short invoke1_Int16([JSMarshalAs<JSType.Number>] short value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static short EchoInt16([JSMarshalAs<JSType.Number>] short arg1) + { + return arg1; + } + #endregion Int16 + + #region Int52 + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial long echo1_Int52([JSMarshalAs<JSType.Number>] long value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Int52([JSMarshalAs<JSType.Number>] long value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial long retrieve1_Int52(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Int52([JSMarshalAs<JSType.Number>] long value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial long throw1_Int52([JSMarshalAs<JSType.Number>] long value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial long invoke1_Int52([JSMarshalAs<JSType.Number>] long value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static long EchoInt52([JSMarshalAs<JSType.Number>] long arg1) + { + return arg1; + } + #endregion Int52 + + #region BigInt64 + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long echo1_BigInt64([JSMarshalAs<JSType.BigInt>] long value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_BigInt64([JSMarshalAs<JSType.BigInt>] long value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long retrieve1_BigInt64(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_BigInt64([JSMarshalAs<JSType.BigInt>] long value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long throw1_BigInt64([JSMarshalAs<JSType.BigInt>] long value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long invoke1_BigInt64([JSMarshalAs<JSType.BigInt>] long value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.BigInt>] + public static long EchoBigInt64([JSMarshalAs<JSType.BigInt>] long arg1) + { + return arg1; + } + #endregion BigInt64 + + #region Double + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double echo1_Double([JSMarshalAs<JSType.Number>] double value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Double([JSMarshalAs<JSType.Number>] double value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double retrieve1_Double(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Double([JSMarshalAs<JSType.Number>] double value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double throw1_Double([JSMarshalAs<JSType.Number>] double value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double invoke1_Double([JSMarshalAs<JSType.Number>] double value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static double EchoDouble([JSMarshalAs<JSType.Number>] double arg1) + { + return arg1; + } + #endregion Double + + #region Single + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial float echo1_Single([JSMarshalAs<JSType.Number>] float value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_Single([JSMarshalAs<JSType.Number>] float value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial float retrieve1_Single(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_Single([JSMarshalAs<JSType.Number>] float value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial float throw1_Single([JSMarshalAs<JSType.Number>] float value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial float invoke1_Single([JSMarshalAs<JSType.Number>] float value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static float EchoSingle([JSMarshalAs<JSType.Number>] float arg1) + { + return arg1; + } + #endregion Single + + #region IntPtr + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr echo1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr retrieve1_IntPtr(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr throw1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr invoke1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static IntPtr EchoIntPtr([JSMarshalAs<JSType.Number>] IntPtr arg1) + { + return arg1; + } + #endregion IntPtr + + #region VoidPtr + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal unsafe static partial void* echo1_VoidPtr([JSMarshalAs<JSType.Number>] void* value); + [JSImport("store1", "JavaScriptTestHelper")] + internal unsafe static partial void store1_VoidPtr([JSMarshalAs<JSType.Number>] void* value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal unsafe static partial void* retrieve1_VoidPtr(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal unsafe static partial bool identity1_VoidPtr([JSMarshalAs<JSType.Number>] void* value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal unsafe static partial void* throw1_VoidPtr([JSMarshalAs<JSType.Number>] void* value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal unsafe static partial void* invoke1_VoidPtr([JSMarshalAs<JSType.Number>] void* value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public unsafe static void* EchoVoidPtr([JSMarshalAs<JSType.Number>] void* arg1) + { + return arg1; + } + #endregion VoidPtr + + #region DateTime + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime echo1_DateTime([JSMarshalAs<JSType.Date>] DateTime value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_DateTime([JSMarshalAs<JSType.Date>] DateTime value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime retrieve1_DateTime(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_DateTime([JSMarshalAs<JSType.Date>] DateTime value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime throw1_DateTime([JSMarshalAs<JSType.Date>] DateTime value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime invoke1_DateTime([JSMarshalAs<JSType.Date>] DateTime value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Date>] + public static DateTime EchoDateTime([JSMarshalAs<JSType.Date>] DateTime arg1) + { + return arg1; + } + #endregion DateTime + + #region DateTimeOffset + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTimeOffset echo1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTimeOffset retrieve1_DateTimeOffset(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTimeOffset throw1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTimeOffset invoke1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Date>] + public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset arg1) + { + return arg1; + } + #endregion DateTimeOffset + + #region NullableBoolean + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool? echo1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool? retrieve1_NullableBoolean(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool? throw1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool? invoke1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Boolean>] + public static bool? EchoNullableBoolean([JSMarshalAs<JSType.Boolean>] bool? arg1) + { + return arg1; + } + #endregion NullableBoolean + + #region NullableInt32 + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int? echo1_NullableInt32([JSMarshalAs<JSType.Number>] int? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableInt32([JSMarshalAs<JSType.Number>] int? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int? retrieve1_NullableInt32(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableInt32([JSMarshalAs<JSType.Number>] int? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int? throw1_NullableInt32([JSMarshalAs<JSType.Number>] int? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial int? invoke1_NullableInt32([JSMarshalAs<JSType.Number>] int? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static int? EchoNullableInt32([JSMarshalAs<JSType.Number>] int? arg1) + { + return arg1; + } + #endregion NullableInt32 + + #region NullableBigInt64 + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long? echo1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long? retrieve1_NullableBigInt64(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long? throw1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.BigInt>] + internal static partial long? invoke1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.BigInt>] + public static long? EchoNullableBigInt64([JSMarshalAs<JSType.BigInt>] long? arg1) + { + return arg1; + } + #endregion NullableBigInt64 + + #region NullableIntPtr + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr? echo1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr? retrieve1_NullableIntPtr(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr? throw1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial IntPtr? invoke1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static IntPtr? EchoNullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? arg1) + { + return arg1; + } + #endregion NullableIntPtr + + #region NullableDouble + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double? echo1_NullableDouble([JSMarshalAs<JSType.Number>] double? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableDouble([JSMarshalAs<JSType.Number>] double? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double? retrieve1_NullableDouble(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableDouble([JSMarshalAs<JSType.Number>] double? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double? throw1_NullableDouble([JSMarshalAs<JSType.Number>] double? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Number>] + internal static partial double? invoke1_NullableDouble([JSMarshalAs<JSType.Number>] double? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Number>] + public static double? EchoNullableDouble([JSMarshalAs<JSType.Number>] double? arg1) + { + return arg1; + } + #endregion NullableDouble + + #region NullableDateTime + + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime? echo1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime? retrieve1_NullableDateTime(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime? throw1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Date>] + internal static partial DateTime? invoke1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Date>] + public static DateTime? EchoNullableDateTime([JSMarshalAs<JSType.Date>] DateTime? arg1) + { + return arg1; + } + #endregion NullableDateTime + + #region JSObject + [JSImport("echo1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject echo1_JSObject([JSMarshalAs<JSType.Object>] JSObject value); + [JSImport("store1", "JavaScriptTestHelper")] + internal static partial void store1_JSObject([JSMarshalAs<JSType.Object>] JSObject value); + [JSImport("retrieve1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject retrieve1_JSObject(); + [JSImport("identity1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Boolean>] + internal static partial bool identity1_JSObject([JSMarshalAs<JSType.Object>] JSObject value); + [JSImport("throw1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject throw1_JSObject([JSMarshalAs<JSType.Object>] JSObject value); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs<JSType.Object>] + internal static partial JSObject invoke1_JSObject([JSMarshalAs<JSType.Object>] JSObject value, [JSMarshalAs<JSType.String>] string name); + [JSExport] + [return: JSMarshalAs<JSType.Object>] + public static JSObject EchoIJSObject([JSMarshalAs<JSType.Object>] JSObject arg1) + { + return arg1; + } + #endregion JSObject + + static JSObject _module; + public static async Task InitializeAsync() + { + if (_module == null) + { + Log("JavaScriptTestHelper.mjs importing"); + _module = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs"); + Log("JavaScriptTestHelper.mjs imported"); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs new file mode 100644 index 00000000000..f91a9661620 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -0,0 +1,254 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +class JSData { + constructor(name) { + this.name = name; + } + toString() { + return `JSData("${this.name}")`; + } +} + +class JSTestError extends Error { + constructor(message) { + super(message) + } +} + +export function createData(name) { + //console.log(`createData(name:"${name ? name : '<null>'}")`) + return new JSData(name); +} + +export function createException(name) { + //console.log(`createException(name:"${name ? name : '<null>'}")`) + return new JSTestError(name); +} + +export function echo1(arg1) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + + //console.log(`echo1(arg1:${arg1 !== null ? JSON.stringify(arg1): '<null>'})`) + return arg1; +} + +export function echo1view(arg1, edit) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + + // console.log(`echo1view(arg1:${arg1 !== null ? arg1 : '<null>'})`) + // console.log(`echo1view(arg1:${arg1 !== null ? typeof arg1 : '<null>'})`) + const cpy = arg1.slice(); + if (edit) { + cpy[1] = cpy[0] + arg1.set(cpy); + } + return arg1; +} + +export function echo1array(arg1, edit) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + + // console.log(`echo1view(arg1:${arg1 !== null ? arg1 : '<null>'})`) + // console.log(`echo1view(arg1:${arg1 !== null ? typeof arg1 : '<null>'})`) + if (edit) { + arg1[1] = arg1[0] + } + return arg1; +} + +export function echo1large(arg1) { + try { + arg1._large = new Uint8Array(10000000); + } + catch (ex) { + console.log("echo1large " + ex) + } + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + + //console.log(`echo1large(arg1:${arg1 !== null ? typeof arg1: '<null>'})`) + return () => { + console.log("don't call me"); + }; +} + +export function store1(arg1) { + //console.log(`store1(arg1:${arg1 !== null ? arg1 : '<null>'})`) + globalThis.javaScriptTestHelper.store1val = arg1; +} + +export function storeAt(arg1, arg2) { + //console.log(`storeAt(arg1:${arg1 !== null ? arg1 : '<null>'})`) + //console.log(`storeAt(arg2:${arg2 !== null ? arg2 : '<null>'})`) + globalThis.javaScriptTestHelper.store1val = arg1[arg2]; + return arg1[arg2]; +} + +export function retrieve1() { + const val = globalThis.javaScriptTestHelper.store1val; + //console.log(`retrieve1(arg1:${val !== null ? val : '<null>'})`) + return val; +} + +export function throw0() { + //console.log(`throw0()`) + throw new Error('throw-0-msg'); +} + +export function throw1(arg1) { + //console.log(`throw1(arg1:${arg1 !== null ? arg1 : '<null>'})`) + throw new Error('throw1-msg ' + arg1); +} + +export function throwretrieve1() { + const val = globalThis.javaScriptTestHelper.store1val; + //console.log(`retrieve1(arg1:${val !== null ? val : '<null>'})`) + throw new Error('throwretrieve1 ' + val); +} + +export function identity1(arg1) { + const val = globalThis.javaScriptTestHelper.store1val; + //console.log(`compare1(arg1:${arg1 !== null ? arg1 : '<null>'}) with ${val !== null ? val : '<null>'}`) + if (val instanceof Date) { + return arg1.valueOf() == val.valueOf(); + } + if (Number.isNaN(val)) { + return Number.isNaN(arg1); + } + return arg1 === val; +} + +export function getType1() { + const val = globalThis.javaScriptTestHelper.store1val; + const vtype = typeof (val); + // console.log(`getType1(arg1:${vtype !== null ? vtype : '<null>'})`) + return vtype; +} + +export function getClass1() { + const val = globalThis.javaScriptTestHelper.store1val; + const cname = val.constructor.name; + // console.log(`getClass1(arg1:${cname !== null ? cname : '<null>'})`) + return cname; +} + +export function invoke1(arg1, name) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + // console.log(`invoke1: ${name}(arg1:${arg1 !== null ? typeof arg1 : '<null>'})`) + const JavaScriptTestHelper = globalThis.App.EXPORTS.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper + const fn = JavaScriptTestHelper[name]; + + //console.log("invoke1:" + fn.toString()); + const res = fn(arg1); + // console.log(`invoke1: res ${res !== null ? typeof res : '<null>'}`) + return res; +} + +export async function awaitvoid(arg1) { + // console.log("awaitvoid:" + typeof arg1); + await arg1; + // console.log("awaitvoid done"); +} + +export function thenvoid(arg1) { + //console.log("thenvoid:" + typeof arg1); + arg1.then(() => { + // console.log("thenvoid then done"); + }); + //console.log("thenvoid done"); +} + +export async function await1(arg1) { + try { + // console.log("await1:" + typeof arg1); + const value = await arg1; + // console.log("await1 value:" + value); + return value; + } catch (ex) { + // console.log("await1 ex:" + ex); + throw ex; + } +} + +export async function await2(arg1) { + //console.log("await2-1:" + typeof arg1); + await arg1; + //console.log("await2-2:" + typeof arg1); +} + +export async function sleep(ms) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + // console.log("sleep:" + ms); + await new Promise(resolve => setTimeout(resolve, ms)); + // console.log("sleep2:" + ms); + return ms; +} + +export async function forever() { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + // console.log("forever:" + ms); + await new Promise(() => { }); +} + +export function back3(arg1, arg2, arg3) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + try { + if (!(arg1 instanceof Function)) throw new Error('expecting Function!') + //console.log(`back3(arg1:${arg1 !== null ? typeof (arg1) : '<null>'})`) + //console.log(`back3(arg2:${arg2 !== null ? arg2 : '<null>'})`) + //console.log(`back3(arg3:${arg3 !== null ? arg3 : '<null>'})`) + + // call it twice, to make sure it's persistent + arg1(arg2, arg3); + //console.log(`back3(arg2:${arg2 !== null ? arg2 : '<null>'})`) + //console.log(`back3(arg3:${arg3 !== null ? arg3 : '<null>'})`) + + return arg1(arg2, arg3); + } + catch (ex) { + // console.log(`back1 - catch`) + throw ex; + } +} + +export function backback(arg1, arg2, arg3) { + if (globalThis.gc) { + // console.log('globalThis.gc'); + globalThis.gc(); + } + // console.log('backback A') + return (brg1, brg2) => { + // console.log('backback B') + return arg1(brg1 + arg2, brg2 + arg3); + } +} + +export const instance = {} + +globalThis.javaScriptTestHelper = instance; + +// console.log('JavaScriptTestHelper:' Object.keys(globalThis.JavaScriptTestHelper)); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Utils.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Utils.cs new file mode 100644 index 00000000000..ad9be6d83d5 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Utils.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace System.Runtime.InteropServices.JavaScript +{ + public static unsafe partial class Utils + { + [JSImport("INTERNAL.set_property")] + public static partial void SetProperty(JSObject self, string propertyName, + [JSMarshalAs<JSType.Function<JSType.Object>>] Action<JSObject> value); + + [JSImport("INTERNAL.get_property")] + [return: JSMarshalAs<JSType.Function<JSType.Object>>] + public static partial Action<JSObject> GetActionOfJSObjectProperty(JSObject self, string propertyName); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function>] + public static partial Action CreateAction([JSMarshalAs<JSType.String>] string code); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Boolean>>] + public static partial Func<bool> CreateFunctionBool([JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Func<int> CreateFunctionInt([JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Func<long> CreateFunctionLong([JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Func<double> CreateFunctionDouble([JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String>>] + public static partial Func<string> CreateFunctionString([JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Object>>] + public static partial Func<JSObject> CreateFunctionJSObject([JSMarshalAs<JSType.String>] string code); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Boolean>>] + public static partial Action<bool> CreateActionBool([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Action<int> CreateActionInt([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Action<long> CreateActionLong([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number>>] + public static partial Action<double> CreateActionDouble([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String>>] + public static partial Action<string> CreateActionString([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Object>>] + public static partial Action<JSObject> CreateActionJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Boolean, JSType.Boolean>>] + public static partial Func<bool, bool> CreateFunctionBoolBool([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Func<int, int> CreateFunctionIntInt([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Func<long, long> CreateFunctionLongLong([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Func<double, double> CreateFunctionDoubleDouble([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String, JSType.String>>] + public static partial Func<string, string> CreateFunctionStringString([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Object, JSType.Object>>] + public static partial Func<JSObject, JSObject> CreateFunctionJSObjectJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Boolean, JSType.Object>>] + public static partial Func<bool, JSObject> CreateFunctionBoolJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Object>>] + public static partial Func<int, JSObject> CreateFunctionIntJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Object>>] + public static partial Func<long, JSObject> CreateFunctionLongJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Object>>] + public static partial Func<double, JSObject> CreateFunctionDoubleJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String, JSType.Object>>] + public static partial Func<string, JSObject> CreateFunctionStringJSObject([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + + /* TODO + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs(JSType.Function, JSType.Boolean, JSType.Promise)] + public static partial Func<bool, Task<object>> CreateFunctionBoolTask([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs(JSType.Function, JSType.Number, JSType.Promise)] + public static partial Func<int, Task<object>> CreateFunctionIntTask([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs(JSType.Function, JSType.Number, JSType.Promise)] + public static partial Func<long, Task<object>> CreateFunctionLongTask([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs(JSType.Function, JSType.Number, JSType.Promise)] + public static partial Func<double, Task<object>> CreateFunctionDoubleJSTask([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs(JSType.Function, JSType.String, JSType.Promise)] + public static partial Func<string, Task<object>> CreateFunctionStringTask([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string code); + [return: JSMarshalAs(JSType.Function, JSType.Promise)] + public static partial Func<Task<object>> CreateFunctionTask([JSMarshalAs<JSType.String>] string code); + */ + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Action<int, int> CreateActionIntInt([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Action<long, long> CreateActionLongLong([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>] + public static partial Action<double, double> CreateActionDoubleDouble([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String, JSType.String>>] + public static partial Action<string, string> CreateActionStringString([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] + public static partial Func<int, int, int> CreateFunctionIntIntInt([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] + public static partial Func<long, long, long> CreateFunctionLongLongLong([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.Number, JSType.Number, JSType.Number>>] + public static partial Func<double, double, double> CreateFunctionDoubleDoubleDouble([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + [JSImport("globalThis.App.create_function")] + [return: JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.String>>] + public static partial Func<string, string, string> CreateFunctionStringStringString([JSMarshalAs<JSType.String>] string arg1Name, [JSMarshalAs<JSType.String>] string arg2Name, [JSMarshalAs<JSType.String>] string code); + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/ValueType.cs b/src/mono/System.Private.CoreLib/src/System/ValueType.cs index fb97a52cae8..262b30baff0 100644 --- a/src/mono/System.Private.CoreLib/src/System/ValueType.cs +++ b/src/mono/System.Private.CoreLib/src/System/ValueType.cs @@ -12,7 +12,7 @@ namespace System { #if TARGET_BROWSER // Tracking issue https://github.com/dotnet/runtime/issues/47909 - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSFunctionBinding", "System.Runtime.InteropServices.JavaScript")] #endif protected ValueType() { diff --git a/src/mono/sample/wasm/browser/Program.cs b/src/mono/sample/wasm/browser/Program.cs index 66ff814d0ef..eb2506e0f21 100644 --- a/src/mono/sample/wasm/browser/Program.cs +++ b/src/mono/sample/wasm/browser/Program.cs @@ -3,10 +3,11 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.JavaScript; namespace Sample { - public class Test + public partial class Test { public static int Main(string[] args) { @@ -14,10 +15,33 @@ namespace Sample return 0; } - [MethodImpl(MethodImplOptions.NoInlining)] - public static int TestMeaning() + [JSImport("Sample.Test.add")] + internal static partial int Add(int a, int b); + + [JSImport("Sample.Test.sub")] + internal static partial int Sub(int a, int b); + + [JSExport] + internal static int TestMeaning() { - return 42; + // call back to JS via imports + return Add(Sub(80, 40), 2); } + + [JSExport] + internal static bool IsPrime(int number) + { + if (number <= 1) return false; + if (number == 2) return true; + if (number % 2 == 0) return false; + + var boundary = (int)Math.Floor(Math.Sqrt(number)); + + for (int i = 3; i <= boundary; i += 2) + if (number % i == 0) + return false; + + return true; + } } } diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.ES6.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.ES6.Sample.csproj index 7c5e62b94c9..772c4add1c5 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.ES6.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.ES6.Sample.csproj @@ -5,18 +5,15 @@ <DebugSymbols>true</DebugSymbols> <DebugType>embedded</DebugType> <WasmDebugLevel>1</WasmDebugLevel> - <GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample> <RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand> </PropertyGroup> - <ItemGroup> <WasmExtraFilesToDeploy Include="index.html" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> - <PropertyGroup> <_SampleProject>Wasm.Browser.ES6.Sample.csproj</_SampleProject> </PropertyGroup> - <Target Name="RunSample" DependsOnTargets="RunSampleWithBrowser" /> </Project> diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index faba0f1ebee..06f216259f5 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -12,8 +12,16 @@ function wasm_exit(exit_code, reason) { console.log(`WASM EXIT ${exit_code}`); } +function add(a, b) { + return a + b; +} + +function sub(a, b) { + return a - b; +} + try { - const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(() => { + const { MONO, RuntimeBuildInfo, IMPORTS } = await createDotnetRuntime(() => { console.log('user code in createDotnetRuntime'); return { configSrc: "./mono-config.json", @@ -24,11 +32,20 @@ try { } }); console.log('after createDotnetRuntime'); + IMPORTS.Sample = { + Test: { + add, + sub + } + }; - const testMeaning = BINDING.bind_static_method("[Wasm.Browser.ES6.Sample] Sample.Test:TestMeaning"); - const ret = testMeaning(); - document.getElementById("out").innerHTML = `${ret} as computed on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; - console.debug(`ret: ${ret}`); + const exports = await MONO.mono_wasm_get_assembly_exports("Wasm.Browser.ES6.Sample.dll"); + const meaning = exports.Sample.Test.TestMeaning(); + console.debug(`meaning: ${meaning}`); + if (!exports.Sample.Test.IsPrime(meaning)) { + document.getElementById("out").innerHTML = `${meaning} as computed on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + console.debug(`ret: ${meaning}`); + } let exit_code = await MONO.mono_run_main("Wasm.Browser.ES6.Sample.dll", []); wasm_exit(exit_code); diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts index ebd01a86e5b..422d3b1ff66 100644 --- a/src/mono/wasm/runtime/cancelable-promise.ts +++ b/src/mono/wasm/runtime/cancelable-promise.ts @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; -import { wrap_error_root } from "./method-calls"; -import { mono_wasm_new_external_root } from "./roots"; -import { JSHandle, MonoObject, MonoObjectRef, MonoString } from "./types"; -import { Int32Ptr } from "./types/emscripten"; +import { _lookup_js_owned_object } from "./gc-handles"; +import { TaskCallbackHolder } from "./marshal-to-cs"; +import { mono_assert, GCHandle } from "./types"; export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function"); export const promise_control_symbol = Symbol.for("wasm promise_control"); @@ -18,34 +16,20 @@ export function isThenable(js_obj: any): boolean { ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function"); } -export function mono_wasm_cancel_promise_ref(thenable_js_handle: JSHandle, is_exception: Int32Ptr, result_address: MonoObjectRef): void | MonoString { - const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); - try { - const promise = mono_wasm_get_jsobj_from_js_handle(thenable_js_handle); - const promise_control = promise[promise_control_symbol]; - promise_control.reject("OperationCanceledException"); - } - catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - return; - } - finally { - resultRoot.release(); - } -} - export interface PromiseControl { isDone: boolean; resolve: (data?: any) => void; reject: (reason: any) => void; + promise: Promise<any>; } -export function _create_cancelable_promise(afterResolve?: () => void, afterReject?: () => void): { +export function create_cancelable_promise(afterResolve?: () => void, afterReject?: () => void): { promise: Promise<any>, promise_control: PromiseControl } { - let promise_control: PromiseControl | null = null; + let promise_control: PromiseControl = <any>null; const promise = new Promise(function (resolve, reject) { promise_control = { + promise: <any>null, isDone: false, resolve: (data: any) => { if (!promise_control!.isDone) { @@ -67,6 +51,26 @@ export function _create_cancelable_promise(afterResolve?: () => void, afterRejec } }; }); + promise_control.promise = promise; (<any>promise)[promise_control_symbol] = promise_control; return { promise, promise_control: promise_control! }; -}
\ No newline at end of file +} + +export function wrap_as_cancelable_promise<T>(fn: () => Promise<T>): Promise<T> { + const { promise, promise_control } = create_cancelable_promise(); + const inner = fn(); + inner.then((data) => promise_control.resolve(data)).catch((reason) => promise_control.reject(reason)); + return promise; +} + +export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void { + const holder = _lookup_js_owned_object(task_holder_gc_handle) as TaskCallbackHolder; + if (!holder) return; // probably already GC collected + + const promise = holder.promise; + mono_assert(!!promise, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`); + const promise_control = (<any>promise)[promise_control_symbol]; + mono_assert(!!promise_control, () => `Expected promise_control for GCHandle ${task_holder_gc_handle}`); + promise_control.reject("OperationCanceledException"); +} + diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js index 3f8a0c56cda..93aaa3a2e24 100644 --- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js @@ -74,13 +74,11 @@ const linked_functions = [ "mono_wasm_typed_array_copy_to_ref", "mono_wasm_typed_array_from_ref", "mono_wasm_typed_array_copy_from_ref", - "mono_wasm_cancel_promise_ref", - "mono_wasm_web_socket_open_ref", - "mono_wasm_web_socket_send", - "mono_wasm_web_socket_receive", - "mono_wasm_web_socket_close_ref", - "mono_wasm_web_socket_abort", "mono_wasm_compile_function_ref", + "mono_wasm_bind_js_function", + "mono_wasm_invoke_bound_function", + "mono_wasm_bind_cs_function", + "mono_wasm_marshal_promise", // pal_icushim_static.c "mono_wasm_load_icu_data", diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index 2313924d9b9..11bd47ee8a3 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -29,12 +29,12 @@ extern void mono_wasm_typed_array_to_array_ref (int js_handle, int *is_exception extern void mono_wasm_typed_array_copy_to_ref (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception, MonoObject** result); extern void mono_wasm_typed_array_from_ref (int ptr, int begin, int end, int bytes_per_element, int type, int *is_exception, MonoObject** result); extern void mono_wasm_typed_array_copy_from_ref (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception, MonoObject** result); -extern void mono_wasm_cancel_promise_ref (int thenable_js_handle, int *is_exception, MonoString** result); -extern void mono_wasm_web_socket_open_ref (MonoString **uri, MonoArray **subProtocols, MonoDelegate **on_close, int *web_socket_js_handle, int *thenable_js_handle, int *is_exception, MonoObject **result); -extern void mono_wasm_web_socket_send (int webSocket_js_handle, void* buffer_ptr, int offset, int length, int message_type, int end_of_message, int *thenable_js_handle, int *is_exception, MonoObject **result); -extern void mono_wasm_web_socket_receive (int webSocket_js_handle, void* buffer_ptr, int offset, int length, void* response_ptr, int *thenable_js_handle, int *is_exception, MonoObject **result); -extern void mono_wasm_web_socket_close_ref (int webSocket_js_handle, int code, MonoString **reason, int wait_for_close_received, int *thenable_js_handle, int *is_exception, MonoObject **result); -extern void mono_wasm_web_socket_abort (int webSocket_js_handle, int *is_exception, MonoString **result); + +extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString **module_name, void *signature, int* function_js_handle, int *is_exception, MonoObject **result); +extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data); +extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); +extern void mono_wasm_marshal_promise(void *data); + void core_initialize_internals () { @@ -50,12 +50,13 @@ void core_initialize_internals () mono_add_internal_call ("Interop/Runtime::TypedArrayCopyToRef", mono_wasm_typed_array_copy_to_ref); mono_add_internal_call ("Interop/Runtime::TypedArrayFromRef", mono_wasm_typed_array_from_ref); mono_add_internal_call ("Interop/Runtime::TypedArrayCopyFromRef", mono_wasm_typed_array_copy_from_ref); - mono_add_internal_call ("Interop/Runtime::WebSocketOpenRef", mono_wasm_web_socket_open_ref); - mono_add_internal_call ("Interop/Runtime::WebSocketSend", mono_wasm_web_socket_send); - mono_add_internal_call ("Interop/Runtime::WebSocketReceive", mono_wasm_web_socket_receive); - mono_add_internal_call ("Interop/Runtime::WebSocketCloseRef", mono_wasm_web_socket_close_ref); - mono_add_internal_call ("Interop/Runtime::WebSocketAbort", mono_wasm_web_socket_abort); - mono_add_internal_call ("Interop/Runtime::CancelPromiseRef", mono_wasm_cancel_promise_ref); + + mono_add_internal_call ("Interop/Runtime::BindJSFunction", mono_wasm_bind_js_function); + mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); + mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); + mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise); + mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root); + mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root); } // Int8Array | int8_t | byte or SByte (signed byte) diff --git a/src/mono/wasm/runtime/corebindings.ts b/src/mono/wasm/runtime/corebindings.ts index 82be21ce2a2..bd9c0fb12eb 100644 --- a/src/mono/wasm/runtime/corebindings.ts +++ b/src/mono/wasm/runtime/corebindings.ts @@ -5,6 +5,7 @@ import { JSHandle, GCHandle, MonoObjectRef } from "./types"; import { PromiseControl } from "./cancelable-promise"; import { runtimeHelpers } from "./imports"; +// TODO replace all of this with [JSExport] const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMarshalString*/][] = [ ["_get_cs_owned_object_by_js_handle_ref", "GetCSOwnedObjectByJSHandleRef", "iim"], ["_get_cs_owned_object_js_handle_ref", "GetCSOwnedObjectJSHandleRef", "mi"], @@ -16,6 +17,7 @@ const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMar ["_release_js_owned_object_by_gc_handle", "ReleaseJSOwnedObjectByGCHandle", "i"], ["_create_tcs", "CreateTaskSource", ""], + ["_create_task_callback", "CreateTaskCallback", ""], ["_set_tcs_result_ref", "SetTaskSourceResultRef", "iR"], ["_set_tcs_failure", "SetTaskSourceFailure", "is"], ["_get_tcs_task_ref", "GetTaskSourceTaskRef", "im"], @@ -53,6 +55,8 @@ export interface t_CSwraps { _create_date_time_ref(ticks: number, result: MonoObjectRef): void; _create_uri_ref(uri: string, result: MonoObjectRef): void; _is_simple_array_ref(obj: MonoObjectRef): boolean; + + _create_task_callback(): GCHandle; } const wrapped_cs_functions: t_CSwraps = <any>{}; diff --git a/src/mono/wasm/runtime/cs-to-js.ts b/src/mono/wasm/runtime/cs-to-js.ts index 9f5e583dbdb..0f5392ea880 100644 --- a/src/mono/wasm/runtime/cs-to-js.ts +++ b/src/mono/wasm/runtime/cs-to-js.ts @@ -14,9 +14,10 @@ import cwraps from "./cwraps"; import { get_js_owned_object_by_gc_handle_ref, js_owned_gc_handle_symbol, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy, _lookup_js_owned_object } from "./gc-handles"; import { mono_method_get_call_signature_ref, call_method_ref, wrap_error_root } from "./method-calls"; import { js_to_mono_obj_root } from "./js-to-cs"; -import { _are_promises_supported, _create_cancelable_promise } from "./cancelable-promise"; +import { _are_promises_supported, create_cancelable_promise } from "./cancelable-promise"; import { getU32, getI32, getF32, getF64 } from "./memory"; import { Int32Ptr, VoidPtr } from "./types/emscripten"; +import { ManagedObject } from "./marshal"; const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke"); const delegate_invoke_signature_symbol = Symbol.for("wasm delegate_invoke_signature"); @@ -296,7 +297,7 @@ function _unbox_task_root_as_promise(root: WasmRoot<MonoObject>) { if (!result) { const explicitFinalization = () => teardown_managed_proxy(result, gc_handle); - const { promise, promise_control } = _create_cancelable_promise(explicitFinalization, explicitFinalization); + const { promise, promise_control } = create_cancelable_promise(explicitFinalization, explicitFinalization); // note that we do not implement promise/task roundtrip // With more complexity we could recover original instance when this promise is marshaled back to C#. @@ -335,7 +336,7 @@ export function _unbox_ref_type_root_as_js_object(root: WasmRoot<MonoObject>): a // If the JS object for this gc_handle was already collected (or was never created) if (is_nullish(result)) { - result = {}; + result = new ManagedObject(); // keep the gc_handle so that we could easily convert it back to original C# object for roundtrip result[js_owned_gc_handle_symbol] = gc_handle; diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index c4892f33dbf..b2963790bd2 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -8,6 +8,7 @@ import { MonoType, MonoObjectRef, MonoStringRef } from "./types"; import { Module } from "./imports"; +import { JSMarshalerArguments } from "./marshal"; import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten"; const fn_signatures: [ident: string, returnType: string | null, argTypes?: string[], opts?: any][] = [ @@ -37,6 +38,7 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin ["mono_wasm_assembly_load", "number", ["string"]], ["mono_wasm_find_corlib_class", "number", ["string", "string"]], ["mono_wasm_assembly_find_class", "number", ["number", "string", "string"]], + ["mono_wasm_runtime_run_module_cctor", "void", ["number"]], ["mono_wasm_find_corlib_type", "number", ["string", "string"]], ["mono_wasm_assembly_find_type", "number", ["number", "string", "string"]], ["mono_wasm_assembly_find_method", "number", ["number", "string", "number"]], @@ -79,6 +81,7 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin ["mono_wasm_enable_on_demand_gc", "void", ["number"]], ["mono_profiler_init_aot", "void", ["number"]], ["mono_wasm_exec_regression", "number", ["number", "string"]], + ["mono_wasm_invoke_method_bound", "number", ["number", "number"]], ["mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]], ["mono_wasm_copy_managed_pointer", "void", ["number", "number"]], ["mono_wasm_i52_to_f64", "number", ["number", "number"]], @@ -182,12 +185,14 @@ export interface t_Cwraps { mono_wasm_set_main_args(argc: number, argv: VoidPtr): void; mono_profiler_init_aot(desc: string): void; mono_wasm_exec_regression(verbose_level: number, image: string): number; + mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments): MonoString; mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void; mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void; - mono_wasm_i52_to_f64 (source: VoidPtr, error: Int32Ptr) : number; - mono_wasm_u52_to_f64 (source: VoidPtr, error: Int32Ptr) : number; - mono_wasm_f64_to_i52 (destination: VoidPtr, value: number) : I52Error; - mono_wasm_f64_to_u52 (destination: VoidPtr, value: number) : I52Error; + mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number; + mono_wasm_u52_to_f64(source: VoidPtr, error: Int32Ptr): number; + mono_wasm_f64_to_i52(destination: VoidPtr, value: number): I52Error; + mono_wasm_f64_to_u52(destination: VoidPtr, value: number): I52Error; + mono_wasm_runtime_run_module_cctor(assembly: MonoAssembly): void; } const wrapped_c_functions: t_Cwraps = <any>{}; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0f2110f53d0..dd58eb3e3f2 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -376,6 +376,49 @@ declare function getF64(offset: _MemOffset): number; declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise<void>; declare function mono_run_main(main_assembly_name: string, args: string[]): Promise<number>; +interface IDisposable { + dispose(): void; + get isDisposed(): boolean; +} +declare class ManagedObject implements IDisposable { + dispose(): void; + get isDisposed(): boolean; + toString(): string; +} +declare class ManagedError extends Error implements IDisposable { + constructor(message: string); + get stack(): string | undefined; + dispose(): void; + get isDisposed(): boolean; + toString(): string; +} +declare enum MemoryViewType { + Byte = 0, + Int32 = 1, + Double = 2 +} +interface IMemoryView { + /** + * copies elements from provided source to the wasm memory. + * target has to have the elements of the same type as the underlying C# array. + * same as TypedArray.set() + */ + set(source: TypedArray, targetOffset?: number): void; + /** + * copies elements from wasm memory to provided target. + * target has to have the elements of the same type as the underlying C# array. + */ + copyTo(target: TypedArray, sourceOffset?: number): void; + /** + * same as TypedArray.slice() + */ + slice(start?: number, end?: number): TypedArray; + get length(): number; + get byteLength(): number; +} + +declare function mono_wasm_get_assembly_exports(assembly: string): Promise<any>; + declare const MONO: { mono_wasm_setenv: typeof mono_wasm_setenv; mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; @@ -390,6 +433,7 @@ declare const MONO: { mono_wasm_release_roots: typeof mono_wasm_release_roots; mono_run_main: typeof mono_run_main; mono_run_main_and_exit: typeof mono_run_main_and_exit; + mono_wasm_get_assembly_exports: typeof mono_wasm_get_assembly_exports; mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number; mono_wasm_load_runtime: (unused: string, debug_level: number) => void; config: MonoConfig | MonoConfigError; @@ -490,4 +534,33 @@ declare global { function getDotnetRuntime(runtimeId: number): DotnetPublicAPI | undefined; } -export { BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, MONOType, MonoArray, MonoObject, MonoString, VoidPtr, createDotnetRuntime as default }; +/** + * Span class is JS wrapper for System.Span<T>. This view doesn't own the memory, nor pin the underlying array. + * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. + * It is disposed at the end of the call to JS. + */ +declare class Span implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} +/** + * ArraySegment class is JS wrapper for System.ArraySegment<T>. + * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. + * User could dispose it manualy. + */ +declare class ArraySegment implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} + +export { ArraySegment, BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, IMemoryView, MONOType, ManagedError, ManagedObject, MemoryViewType, MonoArray, MonoObject, MonoString, Span, VoidPtr, createDotnetRuntime as default }; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index fb0b15101fd..c8ea3e445d5 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -447,7 +447,6 @@ get_native_to_interp (MonoMethod *method, void *extra_arg) void mono_initialize_internals () { mono_add_internal_call ("Interop/Runtime::InvokeJS", mono_wasm_invoke_js); - // TODO: what happens when two types in different assemblies have the same FQN? // Blazor specific custom routines - see dotnet_support.js for backing code mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJS", mono_wasm_invoke_js_blazor); @@ -655,6 +654,22 @@ mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, co return result; } +extern int mono_runtime_run_module_cctor (MonoImage *image, MonoError *error); + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_runtime_run_module_cctor (MonoAssembly *assembly) +{ + assert (assembly); + MonoError error; + MONO_ENTER_GC_UNSAFE; + MonoImage *image = mono_assembly_get_image (assembly); + if (!mono_runtime_run_module_cctor(image, &error)) { + //g_print ("Failed to run module constructor due to %s\n", mono_error_get_message (error)); + } + MONO_EXIT_GC_UNSAFE; +} + + EMSCRIPTEN_KEEPALIVE MonoMethod* mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments) { @@ -742,6 +757,25 @@ mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[ return result; } +EMSCRIPTEN_KEEPALIVE MonoObject* +mono_wasm_invoke_method_bound (MonoMethod *method, void* args)// JSMarshalerArguments +{ + MonoObject *exc = NULL; + MonoObject *res; + + void *invoke_args[1] = { args }; + + mono_runtime_invoke (method, NULL, invoke_args, &exc); + if (exc) { + MonoObject *exc2 = NULL; + res = (MonoObject*)mono_object_to_string (exc, &exc2); + if (exc2) + res = (MonoObject*) mono_string_new (root_domain, "Exception Double Fault"); + return res; + } + return NULL; +} + EMSCRIPTEN_KEEPALIVE MonoMethod* mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint) { diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 19fd04fecd5..36e8a8162ae 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -111,13 +111,11 @@ const linked_functions = [ "mono_wasm_typed_array_copy_to_ref", "mono_wasm_typed_array_from_ref", "mono_wasm_typed_array_copy_from_ref", - "mono_wasm_cancel_promise_ref", - "mono_wasm_web_socket_open_ref", - "mono_wasm_web_socket_send", - "mono_wasm_web_socket_receive", - "mono_wasm_web_socket_close_ref", - "mono_wasm_web_socket_abort", "mono_wasm_compile_function_ref", + "mono_wasm_bind_js_function", + "mono_wasm_invoke_bound_function", + "mono_wasm_bind_cs_function", + "mono_wasm_marshal_promise", // pal_icushim_static.c "mono_wasm_load_icu_data", diff --git a/src/mono/wasm/runtime/export-types.ts b/src/mono/wasm/runtime/export-types.ts index 3cb8c0df090..057ed837709 100644 --- a/src/mono/wasm/runtime/export-types.ts +++ b/src/mono/wasm/runtime/export-types.ts @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. import { BINDINGType, DotnetPublicAPI, MONOType } from "./exports"; +import { IDisposable, IMemoryView, ManagedError, ManagedObject, MemoryViewType } from "./marshal"; import { DotnetModuleConfig, MonoArray, MonoObject, MonoString } from "./types"; -import { EmscriptenModule, VoidPtr } from "./types/emscripten"; +import { EmscriptenModule, TypedArray, VoidPtr } from "./types/emscripten"; // ----------------------------------------------------------- // this files has all public exports from the dotnet.js module @@ -20,10 +21,41 @@ declare global { export default createDotnetRuntime; +/** + * Span class is JS wrapper for System.Span<T>. This view doesn't own the memory, nor pin the underlying array. + * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. + * It is disposed at the end of the call to JS. + */ +declare class Span implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} + +/** + * ArraySegment class is JS wrapper for System.ArraySegment<T>. + * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. + * User could dispose it manualy. + */ +declare class ArraySegment implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} + export { VoidPtr, MonoObject, MonoString, MonoArray, BINDINGType, MONOType, EmscriptenModule, - DotnetPublicAPI, DotnetModuleConfig, CreateDotnetRuntimeType + DotnetPublicAPI, DotnetModuleConfig, CreateDotnetRuntimeType, + IMemoryView, MemoryViewType, ManagedObject, ManagedError, Span, ArraySegment }; diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 34da483b5ec..34646ca213b 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -67,7 +67,13 @@ import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; import { EmscriptenModule } from "./types/emscripten"; import { mono_run_main, mono_run_main_and_exit } from "./run"; +import { dynamic_import, get_global_this, get_property, get_typeof_property, has_property, mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, set_property } from "./invoke-js"; +import { mono_wasm_bind_cs_function, mono_wasm_get_assembly_exports } from "./invoke-cs"; +import { mono_wasm_marshal_promise } from "./marshal-to-js"; +import { ws_wasm_abort, ws_wasm_close, ws_wasm_create, ws_wasm_open, ws_wasm_receive, ws_wasm_send } from "./web-socket"; +import { http_wasm_abort_request, http_wasm_abort_response, http_wasm_create_abort_controler, http_wasm_fetch, http_wasm_fetch_bytes, http_wasm_get_response_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes, http_wasm_supports_streaming_response } from "./http"; import { diagnostics } from "./diagnostics"; +import { mono_wasm_cancel_promise } from "./cancelable-promise"; import { dotnet_browser_can_use_subtle_crypto_impl, dotnet_browser_simple_digest_hash, @@ -75,8 +81,6 @@ import { dotnet_browser_encrypt_decrypt, dotnet_browser_derive_bits, } from "./crypto-worker"; -import { mono_wasm_cancel_promise_ref } from "./cancelable-promise"; -import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket"; import { mono_wasm_pthread_on_pthread_attached, afterThreadInitTLS } from "./pthreads/worker"; import { afterLoadWasmModuleToWorker } from "./pthreads/browser"; @@ -95,6 +99,7 @@ const MONO = { mono_wasm_release_roots, mono_run_main, mono_run_main_and_exit, + mono_wasm_get_assembly_exports, // for Blazor's future! mono_wasm_add_assembly: cwraps.mono_wasm_add_assembly, @@ -393,12 +398,10 @@ export const __linker_exports: any = { mono_wasm_typed_array_copy_to_ref, mono_wasm_typed_array_from_ref, mono_wasm_typed_array_copy_from_ref, - mono_wasm_cancel_promise_ref, - mono_wasm_web_socket_open_ref, - mono_wasm_web_socket_send, - mono_wasm_web_socket_receive, - mono_wasm_web_socket_close_ref, - mono_wasm_web_socket_abort, + mono_wasm_bind_js_function, + mono_wasm_invoke_bound_function, + mono_wasm_bind_cs_function, + mono_wasm_marshal_promise, // also keep in sync with pal_icushim_static.c mono_wasm_load_icu_data, @@ -451,6 +454,37 @@ const INTERNAL: any = { mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached, mono_wasm_runtime_is_ready: <boolean>runtimeHelpers.mono_wasm_runtime_is_ready, + + // interop + get_property, + set_property, + has_property, + get_typeof_property, + get_global_this, + get_dotnet_instance, + dynamic_import, + + // BrowserWebSocket + mono_wasm_cancel_promise, + ws_wasm_create, + ws_wasm_open, + ws_wasm_send, + ws_wasm_receive, + ws_wasm_close, + ws_wasm_abort, + + // BrowserHttpHandler + http_wasm_supports_streaming_response, + http_wasm_create_abort_controler, + http_wasm_abort_request, + http_wasm_abort_response, + http_wasm_fetch, + http_wasm_fetch_bytes, + http_wasm_get_response_header_names, + http_wasm_get_response_header_values, + http_wasm_get_response_bytes, + http_wasm_get_response_length, + http_wasm_get_streamed_response_bytes, }; // this represents visibility in the javascript diff --git a/src/mono/wasm/runtime/gc-common.h b/src/mono/wasm/runtime/gc-common.h index 4ea2bb06797..92789e641a3 100644 --- a/src/mono/wasm/runtime/gc-common.h +++ b/src/mono/wasm/runtime/gc-common.h @@ -61,3 +61,9 @@ static void copy_volatile (PPVOLATILE(MonoObject) destination, PPVOLATILE(MonoObject) source) { mono_gc_wbarrier_generic_store_atomic((void*)destination, (MonoObject*)(*source)); } + +EMSCRIPTEN_KEEPALIVE int +mono_wasm_register_root (char *start, size_t size, const char *name); + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_deregister_root (char *addr);
\ No newline at end of file diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts new file mode 100644 index 00000000000..d8249fad0b9 --- /dev/null +++ b/src/mono/wasm/runtime/http.ts @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { wrap_as_cancelable_promise } from "./cancelable-promise"; +import { MemoryViewType, Span } from "./marshal"; +import { mono_assert } from "./types"; +import { VoidPtr } from "./types/emscripten"; + +export function http_wasm_supports_streaming_response(): boolean { + return typeof Response !== "undefined" && "body" in Response.prototype && typeof ReadableStream === "function"; +} + +export function http_wasm_create_abort_controler(): AbortController { + return new AbortController(); +} + +export function http_wasm_abort_request(abort_controller: AbortController): void { + abort_controller.abort(); +} + +export function http_wasm_abort_response(res: ResponseExtension): void { + res.__abort_controller.abort(); + if (res.__reader) { + res.__reader.cancel(); + } +} + +export function http_wasm_fetch_bytes(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, bodyPtr: VoidPtr, bodyLength: number): Promise<ResponseExtension> { + // the bufferPtr is pinned by the caller + const view = new Span(bodyPtr, bodyLength, MemoryViewType.Byte); + const copy = view.slice() as Uint8Array; + return http_wasm_fetch(url, header_names, header_values, option_names, option_values, abort_controller, copy); +} + +export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: string | Uint8Array | null): Promise<ResponseExtension> { + mono_assert(url && typeof url === "string", "expected url string"); + mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays"); + mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays"); + const headers = new Headers(); + for (let i = 0; i < header_names.length; i++) { + headers.append(header_names[i], header_values[i]); + } + const options: any = { + body, + headers, + signal: abort_controller.signal + }; + for (let i = 0; i < option_names.length; i++) { + options[option_names[i]] = option_values[i]; + } + + return wrap_as_cancelable_promise(async () => { + const res = await fetch(url, options) as ResponseExtension; + res.__abort_controller = abort_controller; + return res; + }); +} + +function get_response_headers(res: ResponseExtension): void { + if (!res.__headerNames) { + res.__headerNames = []; + res.__headerValues = []; + const entries: Iterable<string[]> = (<any>res.headers).entries(); + + for (const pair of entries) { + res.__headerNames.push(pair[0]); + res.__headerValues.push(pair[1]); + } + } +} + +export function http_wasm_get_response_header_names(res: ResponseExtension): string[] { + get_response_headers(res); + return res.__headerNames; +} + +export function http_wasm_get_response_header_values(res: ResponseExtension): string[] { + get_response_headers(res); + return res.__headerValues; +} + +export function http_wasm_get_response_length(res: ResponseExtension): Promise<number> { + return wrap_as_cancelable_promise(async () => { + const buffer = await res.arrayBuffer(); + res.__buffer = buffer; + res.__source_offset = 0; + return buffer.byteLength; + }); +} + +export function http_wasm_get_response_bytes(res: ResponseExtension, view: Span): number { + mono_assert(res.__buffer, "expected resoved arrayBuffer"); + if (res.__source_offset == res.__buffer!.byteLength) { + return 0; + } + const source_view = new Uint8Array(res.__buffer!, res.__source_offset); + view.set(source_view, 0); + const bytes_read = Math.min(view.byteLength, source_view.byteLength); + res.__source_offset += bytes_read; + return bytes_read; +} + +export async function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise<number> { + // the bufferPtr is pinned by the caller + const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); + return wrap_as_cancelable_promise(async () => { + if (!res.__chunk && res.body) { + res.__reader = res.body.getReader(); + res.__chunk = await res.__reader.read(); + res.__source_offset = 0; + } + + let target_offset = 0; + let bytes_read = 0; + // loop until end of browser stream or end of C# buffer + while (res.__reader && res.__chunk && !res.__chunk.done) { + const remaining_source = res.__chunk.value.byteLength - res.__source_offset; + if (remaining_source === 0) { + res.__chunk = await res.__reader.read(); + res.__source_offset = 0; + continue;// are we done yet + } + + const remaining_target = view.byteLength - target_offset; + const bytes_copied = Math.min(remaining_source, remaining_target); + const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied); + + // copy available bytes + view.set(source_view, target_offset); + target_offset += bytes_copied; + bytes_read += bytes_copied; + res.__source_offset += bytes_copied; + + if (target_offset == view.byteLength) { + return bytes_read; + } + } + return bytes_read; + }); +} + +interface ResponseExtension extends Response { + __buffer?: ArrayBuffer + __reader?: ReadableStreamDefaultReader<Uint8Array> + __chunk?: ReadableStreamDefaultReadResult<Uint8Array> + __source_offset: number + __abort_controller: AbortController + __headerNames: string[]; + __headerValues: string[]; +}
\ No newline at end of file diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts new file mode 100644 index 00000000000..0201be0d2d9 --- /dev/null +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { MonoObject, MonoString } from "./export-types"; +import { EXPORTS, Module, runtimeHelpers } from "./imports"; +import { generate_arg_marshal_to_cs } from "./marshal-to-cs"; +import { marshal_exception_to_js, generate_arg_marshal_to_js } from "./marshal-to-js"; +import { + JSMarshalerArguments, JavaScriptMarshalerArgSize, JSFunctionSignature, + JSMarshalerTypeSize, JSMarshalerSignatureHeaderSize, + get_arg, get_sig, set_arg_type, + get_signature_argument_count, is_args_exception, bound_cs_function_symbol, get_signature_version, MarshalerType, +} from "./marshal"; +import { parseFQN, wrap_error_root } from "./method-calls"; +import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; +import { conv_string, conv_string_root } from "./strings"; +import { mono_assert, MonoObjectRef, MonoStringRef } from "./types"; +import { Int32Ptr } from "./types/emscripten"; +import cwraps, { wrap_c_function } from "./cwraps"; +import { find_method } from "./method-binding"; +import { assembly_load } from "./class-loader"; + +const exportedMethods = new Map<string, Function>(); + +export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const fqn_root = mono_wasm_new_external_root<MonoString>(fully_qualified_name), resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + const anyModule = Module as any; + try { + const version = get_signature_version(signature); + mono_assert(version === 1, () => `Signature version ${version} mismatch.`); + + const args_count = get_signature_argument_count(signature); + const js_fqn = conv_string_root(fqn_root)!; + mono_assert(js_fqn, "fully_qualified_name must be string"); + + if (runtimeHelpers.config.diagnostic_tracing) { + console.trace(`MONO_WASM: Binding [JSExport] ${js_fqn}`); + } + + const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); + + const asm = assembly_load(assembly); + if (!asm) + throw new Error("Could not find assembly: " + assembly); + + const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); + if (!klass) + throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); + + const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; + const method = find_method(klass, wrapper_name, -1); + if (!method) + throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); + + const closure: any = { + method, get_arg, signature, + stackSave: anyModule.stackSave, stackAlloc: anyModule.stackAlloc, stackRestore: anyModule.stackRestore, + conv_string, + mono_wasm_new_root, init_void, init_result, /*init_argument,*/ marshal_exception_to_js, is_args_exception, + mono_wasm_invoke_method_bound: wrap_c_function("mono_wasm_invoke_method_bound"), + }; + const bound_js_function_name = "_bound_cs_" + `${namespace}_${classname}_${methodname}`.replace(/\./g, "_"); + let body = `//# sourceURL=https://mono-wasm.invalid/${bound_js_function_name} \n`; + let bodyToCs = ""; + let converter_names = ""; + + for (let index = 0; index < args_count; index++) { + const arg_offset = (index + 2) * JavaScriptMarshalerArgSize; + const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize; + const sig = get_sig(signature, index + 2); + const { converters, call_body } = generate_arg_marshal_to_cs(sig, index + 2, arg_offset, sig_offset, `arguments[${index}]`, closure); + converter_names += converters; + bodyToCs += call_body; + } + const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_js(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure); + converter_names += res_converters; + + body += `const { method, get_arg, signature, stackSave, stackAlloc, stackRestore, mono_wasm_new_root, conv_string, init_void, init_result, init_argument, marshal_exception_to_js, is_args_exception, mono_wasm_invoke_method_bound ${converter_names} } = closure;\n`; + // TODO named arguments instead of arguments keyword + body += `return function ${bound_js_function_name} () {\n`; + if (res_marshaler_type === MarshalerType.String) { + body += "let root = null;\n"; + } + body += "const sp = stackSave();\n"; + body += "try {\n"; + body += ` const args = stackAlloc(${(args_count + 2) * JavaScriptMarshalerArgSize});\n`; + if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) { + if (res_marshaler_type === MarshalerType.String) { + body += " root = mono_wasm_new_root(0);\n"; + body += " init_result(args);\n"; + } + else { + body += " init_result(args);\n"; + } + } else { + body += " init_void(args);\n"; + } + + body += bodyToCs; + + body += " const fail = mono_wasm_invoke_method_bound(method, args);\n"; + body += " if (fail) throw new Error(\"ERR22: Unexpected error: \" + conv_string(fail));\n"; + body += " if (is_args_exception(args)) throw marshal_exception_to_js(get_arg(args, 0));\n"; + if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) { + body += res_call_body; + } + + if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) { + body += "return js_result;\n"; + } + + body += "} finally {\n"; + body += " stackRestore(sp);\n"; + if (res_marshaler_type === MarshalerType.String) { + body += " if(root) root.release()\n"; + } + body += "}}"; + const factory = new Function("closure", body); + const bound_fn = factory(closure); + bound_fn[bound_cs_function_symbol] = true; + + exportedMethods.set(js_fqn, bound_fn); + _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); + } + catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); + } finally { + resultRoot.release(); + fqn_root.release(); + } +} + +function init_void(args: JSMarshalerArguments) { + mono_assert(args && (<any>args) % 8 == 0, "Arg alignment"); + const exc = get_arg(args, 0); + set_arg_type(exc, MarshalerType.None); + + const res = get_arg(args, 1); + set_arg_type(res, MarshalerType.None); +} + +function init_result(args: JSMarshalerArguments) { + mono_assert(args && (<any>args) % 8 == 0, "Arg alignment"); + const exc = get_arg(args, 0); + set_arg_type(exc, MarshalerType.None); + + const res = get_arg(args, 1); + set_arg_type(res, MarshalerType.None); +} + +export const exportsByAssembly: Map<string, any> = new Map(); + +function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void { + let scope: any = EXPORTS; + let parts = `${namespace}.${classname}`.split("."); + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + let newscope = scope[part]; + if (!newscope) { + newscope = {}; + scope[part] = newscope; + } + mono_assert(newscope, () => `${part} not found while looking up ${classname}`); + scope = newscope; + } + + if (!scope[methodname]) { + scope[methodname] = fn; + } + scope[`${methodname}.${signature_hash}`] = fn; + + // do it again for per assemly scope + let assemblyScope = exportsByAssembly.get(assembly); + if (!assemblyScope) { + assemblyScope = {}; + exportsByAssembly.set(assembly, assemblyScope); + exportsByAssembly.set(assembly + ".dll", assemblyScope); + } + scope = assemblyScope; + parts = `${namespace}.${classname}`.split("."); + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + let newscope = scope[part]; + if (!newscope) { + newscope = {}; + scope[part] = newscope; + } + mono_assert(newscope, () => `${part} not found while looking up ${classname}`); + scope = newscope; + } + + if (!scope[methodname]) { + scope[methodname] = fn; + } + scope[`${methodname}.${signature_hash}`] = fn; +} + +export async function mono_wasm_get_assembly_exports(assembly: string): Promise<any> { + const asm = assembly_load(assembly); + if (!asm) + throw new Error("Could not find assembly: " + assembly); + cwraps.mono_wasm_runtime_run_module_cctor(asm); + + return exportsByAssembly.get(assembly) || {}; +}
\ No newline at end of file diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts new file mode 100644 index 00000000000..0f6b64010b3 --- /dev/null +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./gc-handles"; +import { marshal_exception_to_cs, generate_arg_marshal_to_cs } from "./marshal-to-cs"; +import { get_signature_argument_count, JSMarshalerArguments as JSMarshalerArguments, JavaScriptMarshalerArgSize, JSFunctionSignature as JSFunctionSignature, bound_js_function_symbol, JSMarshalerTypeSize, get_sig, JSMarshalerSignatureHeaderSize, get_signature_version, MarshalerType, get_signature_type } from "./marshal"; +import { setI32 } from "./memory"; +import { wrap_error_root } from "./method-calls"; +import { conv_string_root } from "./strings"; +import { mono_assert, JSHandle, MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "./types"; +import { Int32Ptr } from "./types/emscripten"; +import { IMPORTS, INTERNAL, runtimeHelpers } from "./imports"; +import { generate_arg_marshal_to_js } from "./marshal-to-js"; +import { mono_wasm_new_external_root } from "./roots"; + +export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const function_name_root = mono_wasm_new_external_root<MonoString>(function_name), + module_name_root = mono_wasm_new_external_root<MonoString>(module_name), + resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + try { + const version = get_signature_version(signature); + mono_assert(version === 1, () => `Signature version ${version} mismatch.`); + + const js_function_name = conv_string_root(function_name_root)!; + const js_module_name = conv_string_root(module_name_root)!; + if (runtimeHelpers.config.diagnostic_tracing) { + console.trace(`MONO_WASM: Binding [JSImport] ${js_function_name} from ${js_module_name}`); + } + const fn = mono_wasm_lookup_function(js_function_name, js_module_name); + const args_count = get_signature_argument_count(signature); + + const closure: any = { fn, marshal_exception_to_cs, signature }; + const bound_js_function_name = "_bound_js_" + js_function_name.replace(/\./g, "_"); + let body = `//# sourceURL=https://mono-wasm.invalid/${bound_js_function_name} \n`; + let converter_names = ""; + + + let bodyToJs = ""; + let pass_args = ""; + for (let index = 0; index < args_count; index++) { + const arg_offset = (index + 2) * JavaScriptMarshalerArgSize; + const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize; + const arg_name = `arg${index}`; + const sig = get_sig(signature, index + 2); + const { converters, call_body } = generate_arg_marshal_to_js(sig, index + 2, arg_offset, sig_offset, arg_name, closure); + converter_names += converters; + bodyToJs += call_body; + if (pass_args === "") { + pass_args += arg_name; + } else { + pass_args += `, ${arg_name}`; + } + } + const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_cs(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure); + converter_names += res_converters; + + body += `const { signature, fn, marshal_exception_to_cs ${converter_names} } = closure;\n`; + body += `return function ${bound_js_function_name} (args) { try {\n`; + // body += `console.log("${bound_js_function_name}")\n`; + body += bodyToJs; + + + if (res_marshaler_type === MarshalerType.Void) { + body += ` const js_result = fn(${pass_args});\n`; + body += ` if (js_result !== undefined) throw new Error('Function ${js_function_name} returned unexpected value, C# signature is void');\n`; + } + else if (res_marshaler_type === MarshalerType.Discard) { + body += ` fn(${pass_args});\n`; + } + else { + body += ` const js_result = fn(${pass_args});\n`; + body += res_call_body; + } + + for (let index = 0; index < args_count; index++) { + const sig = get_sig(signature, index + 2); + const marshaler_type = get_signature_type(sig); + if (marshaler_type == MarshalerType.Span) { + const arg_name = `arg${index}`; + body += ` ${arg_name}.dispose();\n`; + } + } + + body += "} catch (ex) {\n"; + body += " marshal_exception_to_cs(args, ex);\n"; + body += "}}"; + const factory = new Function("closure", body); + const bound_fn = factory(closure); + bound_fn[bound_js_function_symbol] = true; + const bound_function_handle = mono_wasm_get_js_handle(bound_fn)!; + setI32(function_js_handle, <any>bound_function_handle); + } catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); + } finally { + resultRoot.release(); + function_name_root.release(); + } +} + +export function mono_wasm_invoke_bound_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void { + const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle); + mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`); + bound_fn(args); +} + +function mono_wasm_lookup_function(function_name: string, js_module_name: string): Function { + mono_assert(function_name && typeof function_name === "string", "function_name must be string"); + + let scope: any = IMPORTS; + const parts = function_name.split("."); + if (js_module_name) { + scope = importedModules.get(js_module_name); + mono_assert(scope, () => `ES6 module ${js_module_name} was not imported yet, please call JSHost.Import() first.`); + } + else if (parts[0] === "INTERNAL") { + scope = INTERNAL; + parts.shift(); + } + else if (parts[0] === "globalThis") { + scope = globalThis; + parts.shift(); + } + + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + const newscope = scope[part]; + mono_assert(newscope, () => `${part} not found while looking up ${function_name}`); + scope = newscope; + } + + const fname = parts[parts.length - 1]; + const fn = scope[fname]; + + mono_assert(typeof (fn) === "function", () => `${function_name} must be a Function but was ${typeof fn}`); + return fn; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function set_property(self: any, name: string, value: any): void { + mono_assert(self, "Null reference"); + self[name] = value; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function get_property(self: any, name: string): any { + mono_assert(self, "Null reference"); + return self[name]; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function has_property(self: any, name: string): boolean { + mono_assert(self, "Null reference"); + return name in self; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function get_typeof_property(self: any, name: string): string { + mono_assert(self, "Null reference"); + return typeof self[name]; +} + +export function get_global_this(): any { + return globalThis; +} + +export const importedModulesPromises: Map<string, Promise<any>> = new Map(); +export const importedModules: Map<string, Promise<any>> = new Map(); + +export async function dynamic_import(module_name: string, module_url: string): Promise<any> { + mono_assert(module_name, "Invalid module_name"); + mono_assert(module_url, "Invalid module_name"); + let promise = importedModulesPromises.get(module_name); + const newPromise = !promise; + if (newPromise) { + if (runtimeHelpers.config.diagnostic_tracing) + console.trace(`MONO_WASM: importing ES6 module '${module_name}' from '${module_url}'`); + promise = import(module_url); + importedModulesPromises.set(module_name, promise); + } + const module = await promise; + if (newPromise) { + importedModules.set(module_name, module); + if (runtimeHelpers.config.diagnostic_tracing) + console.trace(`MONO_WASM: imported ES6 module '${module_name}' from '${module_url}'`); + } + return module; +} + + diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts new file mode 100644 index 00000000000..fff46ba6043 --- /dev/null +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -0,0 +1,651 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { isThenable } from "./cancelable-promise"; +import wrapped_cs_functions from "./corebindings"; +import cwraps from "./cwraps"; +import { assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; +import { Module, runtimeHelpers } from "./imports"; +import { + JSMarshalerArgument, ManagedError, + set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_b8, set_arg_date, + set_arg_length, get_arg, is_args_exception, JavaScriptMarshalerArgSize, get_signature_type, get_signature_arg1_type, get_signature_arg2_type, cs_to_js_marshalers, js_to_cs_marshalers, + MarshalerToCs, MarshalerToJs, get_signature_res_type, JSMarshalerArguments, bound_js_function_symbol, set_arg_u16, JSMarshalerType, array_element_size, get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, MarshalerType, set_arg_i64_big, set_arg_intptr, IDisposable, set_arg_element_type, ManagedObject +} from "./marshal"; +import { marshal_exception_to_js } from "./marshal-to-js"; +import { _zero_region } from "./memory"; +import { conv_string, js_string_to_mono_string_root } from "./strings"; +import { mono_assert, GCHandle, GCHandleNull } from "./types"; +import { TypedArray } from "./types/emscripten"; + +export function initialize_marshalers_to_cs(): void { + if (js_to_cs_marshalers.size == 0) { + js_to_cs_marshalers.set(MarshalerType.Array, _marshal_array_to_cs); + js_to_cs_marshalers.set(MarshalerType.Span, _marshal_span_to_cs); + js_to_cs_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_cs); + js_to_cs_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_cs); + js_to_cs_marshalers.set(MarshalerType.Byte, _marshal_byte_to_cs); + js_to_cs_marshalers.set(MarshalerType.Char, _marshal_char_to_cs); + js_to_cs_marshalers.set(MarshalerType.Int16, _marshal_int16_to_cs); + js_to_cs_marshalers.set(MarshalerType.Int32, _marshal_int32_to_cs); + js_to_cs_marshalers.set(MarshalerType.Int52, _marshal_int52_to_cs); + js_to_cs_marshalers.set(MarshalerType.BigInt64, _marshal_bigint64_to_cs); + js_to_cs_marshalers.set(MarshalerType.Double, _marshal_double_to_cs); + js_to_cs_marshalers.set(MarshalerType.Single, _marshal_float_to_cs); + js_to_cs_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_cs); + js_to_cs_marshalers.set(MarshalerType.DateTime, _marshal_date_time_to_cs); + js_to_cs_marshalers.set(MarshalerType.DateTimeOffset, _marshal_date_time_offset_to_cs); + js_to_cs_marshalers.set(MarshalerType.String, _marshal_string_to_cs); + js_to_cs_marshalers.set(MarshalerType.Exception, marshal_exception_to_cs); + js_to_cs_marshalers.set(MarshalerType.JSException, marshal_exception_to_cs); + js_to_cs_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_cs); + js_to_cs_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_cs); + js_to_cs_marshalers.set(MarshalerType.Task, _marshal_task_to_cs); + js_to_cs_marshalers.set(MarshalerType.Action, _marshal_function_to_cs); + js_to_cs_marshalers.set(MarshalerType.Function, _marshal_function_to_cs); + js_to_cs_marshalers.set(MarshalerType.None, _marshal_null_to_cs);// also void + js_to_cs_marshalers.set(MarshalerType.Discard, _marshal_null_to_cs);// also void + js_to_cs_marshalers.set(MarshalerType.Void, _marshal_null_to_cs);// also void + } +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function generate_arg_marshal_to_cs(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): { + converters: string, + call_body: string, + marshaler_type: MarshalerType +} { + let converters = ""; + let converter_types = ""; + let call_body = ""; + const converter_name = "converter" + index; + let converter_name_arg1 = "null"; + let converter_name_arg2 = "null"; + let converter_name_arg3 = "null"; + let converter_name_res = "null"; + + let marshaler_type = get_signature_type(sig); + if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { + return { + converters, + call_body, + marshaler_type + }; + } + + const marshaler_type_res = get_signature_res_type(sig); + if (marshaler_type_res !== MarshalerType.None) { + const converter = js_to_cs_marshalers.get(marshaler_type_res); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`); + + + if (marshaler_type != MarshalerType.Nullable) { + converter_name_res = "converter" + index + "_res"; + converters += ", " + converter_name_res; + converter_types += " " + MarshalerType[marshaler_type_res]; + closure[converter_name_res] = converter; + } + else { + marshaler_type = marshaler_type_res; + } + } + + const marshaler_type_arg1 = get_signature_arg1_type(sig); + if (marshaler_type_arg1 !== MarshalerType.None) { + const converter = cs_to_js_marshalers.get(marshaler_type_arg1); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`); + + converter_name_arg1 = "converter" + index + "_arg1"; + converters += ", " + converter_name_arg1; + converter_types += " " + MarshalerType[marshaler_type_arg1]; + closure[converter_name_arg1] = converter; + } + + const marshaler_type_arg2 = get_signature_arg2_type(sig); + if (marshaler_type_arg2 !== MarshalerType.None) { + const converter = cs_to_js_marshalers.get(marshaler_type_arg2); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`); + + converter_name_arg2 = "converter" + index + "_arg2"; + converters += ", " + converter_name_arg2; + converter_types += " " + MarshalerType[marshaler_type_arg2]; + closure[converter_name_arg2] = converter; + } + + const marshaler_type_arg3 = get_signature_arg3_type(sig); + if (marshaler_type_arg3 !== MarshalerType.None) { + const converter = cs_to_js_marshalers.get(marshaler_type_arg3); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`); + + converter_name_arg3 = "converter" + index + "_arg3"; + converters += ", " + converter_name_arg3; + converter_types += " " + MarshalerType[marshaler_type_arg3]; + closure[converter_name_arg3] = converter; + } + + const converter = js_to_cs_marshalers.get(marshaler_type); + + const arg_type_name = MarshalerType[marshaler_type]; + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${arg_type_name} (${marshaler_type}) at ${index} `); + + converters += ", " + converter_name; + converter_types += " " + arg_type_name; + closure[converter_name] = converter; + + + if (marshaler_type == MarshalerType.Task) { + call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`; + } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) { + call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg2}); // ${converter_types} \n`; + } else { + call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}); // ${converter_types} \n`; + } + + return { + converters, + call_body, + marshaler_type + }; +} + +function _marshal_bool_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Boolean); + set_arg_b8(arg, value); + } +} + +function _marshal_byte_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Byte); + set_arg_u8(arg, value); + } +} + +function _marshal_char_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Char); + set_arg_u16(arg, value); + } +} + +function _marshal_int16_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Int16); + set_arg_i16(arg, value); + } +} + +function _marshal_int32_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Int32); + set_arg_i32(arg, value); + } +} + +function _marshal_int52_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Int52); + set_arg_i52(arg, value); + } +} + +function _marshal_bigint64_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.BigInt64); + set_arg_i64_big(arg, value); + } +} + +function _marshal_double_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Double); + set_arg_f64(arg, value); + } +} + +function _marshal_float_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.Single); + set_arg_f32(arg, value); + } +} + +function _marshal_intptr_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.IntPtr); + set_arg_intptr(arg, value); + } +} + +function _marshal_date_time_to_cs(arg: JSMarshalerArgument, value: Date): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + mono_assert(value instanceof Date, "Value is not a Date"); + set_arg_type(arg, MarshalerType.DateTime); + set_arg_date(arg, value); + } +} + +function _marshal_date_time_offset_to_cs(arg: JSMarshalerArgument, value: Date): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + mono_assert(value instanceof Date, "Value is not a Date"); + set_arg_type(arg, MarshalerType.DateTimeOffset); + set_arg_date(arg, value); + } +} + +function _marshal_string_to_cs(arg: JSMarshalerArgument, value: string) { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + set_arg_type(arg, MarshalerType.String); + mono_assert(typeof value === "string", "Value is not a String"); + _marshal_string_to_cs_impl(arg, value); + } +} + +function _marshal_string_to_cs_impl(arg: JSMarshalerArgument, value: string) { + const root = get_string_root(arg); + try { + js_string_to_mono_string_root(value, root); + } + finally { + root.release(); + } +} + +function _marshal_null_to_cs(arg: JSMarshalerArgument) { + set_arg_type(arg, MarshalerType.None); +} + +function _marshal_function_to_cs(arg: JSMarshalerArgument, value: Function, _?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs, arg3_converter?: MarshalerToJs): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + return; + } + mono_assert(value && value instanceof Function, "Value is not a Function"); + + // TODO: we could try to cache value -> exising JSHandle + const marshal_function_to_cs_wrapper: any = (args: JSMarshalerArguments) => { + const exc = get_arg(args, 0); + const res = get_arg(args, 1); + const arg1 = get_arg(args, 2); + const arg2 = get_arg(args, 3); + const arg3 = get_arg(args, 4); + + try { + let arg1_js: any = undefined; + let arg2_js: any = undefined; + let arg3_js: any = undefined; + if (arg1_converter) { + arg1_js = arg1_converter(arg1); + } + if (arg2_converter) { + arg2_js = arg2_converter(arg2); + } + if (arg3_converter) { + arg3_js = arg3_converter(arg3); + } + const res_js = value(arg1_js, arg2_js, arg3_js); + if (res_converter) { + res_converter(res, res_js); + } + + } catch (ex) { + marshal_exception_to_cs(exc, ex); + } + }; + + marshal_function_to_cs_wrapper[bound_js_function_symbol] = true; + const bound_function_handle = mono_wasm_get_js_handle(marshal_function_to_cs_wrapper)!; + set_js_handle(arg, bound_function_handle); + set_arg_type(arg, MarshalerType.Function);//TODO or action ? +} + +export class TaskCallbackHolder implements IDisposable { + public promise: Promise<any> + + public constructor(promise: Promise<any>) { + this.promise = promise; + } + + dispose(): void { + teardown_managed_proxy(this, GCHandleNull); + } + + get isDisposed(): boolean { + return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull; + } +} + +function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?: JSMarshalerType, res_converter?: MarshalerToCs) { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + return; + } + mono_assert(isThenable(value), "Value is not a Promise"); + + const anyModule = Module as any; + const gc_handle: GCHandle = wrapped_cs_functions._create_task_callback(); + set_gc_handle(arg, gc_handle); + set_arg_type(arg, MarshalerType.Task); + const holder = new TaskCallbackHolder(value); + setup_managed_proxy(holder, gc_handle); + + value.then(data => { + const sp = anyModule.stackSave(); + try { + const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 3); + const exc = get_arg(args, 0); + set_arg_type(exc, MarshalerType.None); + const res = get_arg(args, 1); + set_arg_type(res, MarshalerType.None); + set_gc_handle(res, <any>gc_handle); + const arg1 = get_arg(args, 2); + if (!res_converter) { + _marshal_cs_object_to_cs(arg1, data); + } else { + res_converter(arg1, data); + } + const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.complete_task_method, args); + if (fail) throw new Error("ERR22: Unexpected error: " + conv_string(fail)); + if (is_args_exception(args)) throw marshal_exception_to_js(exc); + } finally { + anyModule.stackRestore(sp); + } + teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) + }).catch(reason => { + const sp = anyModule.stackSave(); + try { + const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 3); + const res = get_arg(args, 1); + set_arg_type(res, MarshalerType.None); + set_gc_handle(res, gc_handle); + const exc = get_arg(args, 0); + if (typeof reason === "string" || reason === null || reason === undefined) { + reason = new Error(reason || ""); + } + marshal_exception_to_cs(exc, reason); + const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.complete_task_method, args); + if (fail) throw new Error("ERR24: Unexpected error: " + conv_string(fail)); + if (is_args_exception(args)) throw marshal_exception_to_js(exc); + } finally { + anyModule.stackRestore(sp); + } + teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed + }); +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function marshal_exception_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else if (value instanceof ManagedError) { + set_arg_type(arg, MarshalerType.Exception); + // this is managed exception round-trip + const gc_handle = assert_not_disposed(value); + set_gc_handle(arg, gc_handle); + } + else { + mono_assert(typeof value === "object" || typeof value === "string", () => `Value is not an Error ${typeof value}`); + set_arg_type(arg, MarshalerType.JSException); + const message = value.toString(); + _marshal_string_to_cs_impl(arg, message); + + const known_js_handle = value[cs_owned_js_handle_symbol]; + if (known_js_handle) { + set_js_handle(arg, known_js_handle); + } + else { + const js_handle = mono_wasm_get_js_handle(value)!; + set_js_handle(arg, js_handle); + } + } +} + +function _marshal_js_object_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === undefined || value === null) { + set_arg_type(arg, MarshalerType.None); + } + else { + // if value was ManagedObject, it would be double proxied, but the C# signature requires that + mono_assert(value[js_owned_gc_handle_symbol] === undefined, "JSObject proxy of ManagedObject proxy is not supported"); + mono_assert(typeof value === "function" || typeof value === "object", () => `JSObject proxy of ${typeof value} is not supported`); + + set_arg_type(arg, MarshalerType.JSObject); + const js_handle = mono_wasm_get_js_handle(value)!; + set_js_handle(arg, js_handle); + } +} + +function _marshal_cs_object_to_cs(arg: JSMarshalerArgument, value: any): void { + if (value === undefined || value === null) { + set_arg_type(arg, MarshalerType.None); + } + else { + const gc_handle = value[js_owned_gc_handle_symbol]; + const js_type = typeof (value); + if (gc_handle === undefined) { + if (js_type === "string" || js_type === "symbol") { + set_arg_type(arg, MarshalerType.String); + _marshal_string_to_cs_impl(arg, value); + } + else if (js_type === "number") { + set_arg_type(arg, MarshalerType.Double); + set_arg_f64(arg, value); + } + else if (js_type === "bigint") { + // we do it because not all bigint values could fit into Int64 + throw new Error("NotImplementedException: bigint"); + } + else if (js_type === "boolean") { + set_arg_type(arg, MarshalerType.Boolean); + set_arg_b8(arg, value); + } + else if (value instanceof Date) { + set_arg_type(arg, MarshalerType.DateTime); + set_arg_date(arg, value); + } + else if (value instanceof Error) { + set_arg_type(arg, MarshalerType.JSException); + const js_handle = mono_wasm_get_js_handle(value); + set_js_handle(arg, js_handle); + } + else if (value instanceof Uint8Array) { + _marshal_array_to_cs_impl(arg, value, MarshalerType.Byte); + } + else if (value instanceof Float64Array) { + _marshal_array_to_cs_impl(arg, value, MarshalerType.Double); + } + else if (value instanceof Int32Array) { + _marshal_array_to_cs_impl(arg, value, MarshalerType.Int32); + } + else if (Array.isArray(value)) { + _marshal_array_to_cs_impl(arg, value, MarshalerType.Object); + } + else if (value instanceof Int16Array + || value instanceof Int8Array + || value instanceof Uint8ClampedArray + || value instanceof Uint16Array + || value instanceof Uint32Array + || value instanceof Float32Array + ) { + throw new Error("NotImplementedException: TypedArray"); + } + else if (isThenable(value)) { + _marshal_task_to_cs(arg, value); + } + else if (value instanceof Span) { + throw new Error("NotImplementedException: Span"); + } + else if (js_type == "object") { + const js_handle = mono_wasm_get_js_handle(value); + set_arg_type(arg, MarshalerType.JSObject); + set_js_handle(arg, js_handle); + } + else { + throw new Error(`JSObject proxy is not supported for ${js_type} ${value}`); + } + } + else { + assert_not_disposed(value); + if (value instanceof ArraySegment) { + throw new Error("NotImplementedException: ArraySegment"); + } + else if (value instanceof ManagedError) { + set_arg_type(arg, MarshalerType.Exception); + set_gc_handle(arg, gc_handle); + } + else if (value instanceof ManagedObject) { + set_arg_type(arg, MarshalerType.Object); + set_gc_handle(arg, gc_handle); + } else { + throw new Error("NotImplementedException " + js_type); + } + } + } +} + +function _marshal_array_to_cs(arg: JSMarshalerArgument, value: Array<any> | TypedArray, sig?: JSMarshalerType): void { + mono_assert(!!sig, "Expected valid sig paramater"); + const element_type = get_signature_arg1_type(sig); + _marshal_array_to_cs_impl(arg, value, element_type); +} + +function _marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array<any> | TypedArray, element_type: MarshalerType): void { + if (value === null || value === undefined) { + set_arg_type(arg, MarshalerType.None); + } + else { + const element_size = array_element_size(element_type); + mono_assert(element_size != -1, () => `Element type ${MarshalerType[element_type]} not supported`); + const length = value.length; + const buffer_length = element_size * length; + const buffer_ptr = <any>Module._malloc(buffer_length); + if (element_type == MarshalerType.String) { + mono_assert(Array.isArray(value), "Value is not an Array"); + _zero_region(buffer_ptr, buffer_length); + cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs"); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(<any>buffer_ptr, index); + _marshal_string_to_cs(element_arg, value[index]); + } + } + else if (element_type == MarshalerType.Object) { + mono_assert(Array.isArray(value), "Value is not an Array"); + _zero_region(buffer_ptr, buffer_length); + cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs"); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(<any>buffer_ptr, index); + _marshal_cs_object_to_cs(element_arg, value[index]); + } + } + else if (element_type == MarshalerType.JSObject) { + mono_assert(Array.isArray(value), "Value is not an Array"); + _zero_region(buffer_ptr, buffer_length); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(buffer_ptr, index); + _marshal_js_object_to_cs(element_arg, value[index]); + } + } + else if (element_type == MarshalerType.Byte) { + mono_assert(Array.isArray(value) || value instanceof Uint8Array, "Value is not an Array or Uint8Array"); + const targetView = Module.HEAPU8.subarray(<any>buffer_ptr, buffer_ptr + length); + targetView.set(value); + } + else if (element_type == MarshalerType.Int32) { + mono_assert(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array"); + const targetView = Module.HEAP32.subarray(<any>buffer_ptr >> 2, (buffer_ptr >> 2) + length); + targetView.set(value); + } + else if (element_type == MarshalerType.Double) { + mono_assert(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array"); + const targetView = Module.HEAPF64.subarray(<any>buffer_ptr >> 3, (buffer_ptr >> 3) + length); + targetView.set(value); + } + else { + throw new Error("not implemented"); + } + set_arg_intptr(arg, buffer_ptr); + set_arg_type(arg, MarshalerType.Array); + set_arg_element_type(arg, element_type); + set_arg_length(arg, value.length); + } +} + +function _marshal_span_to_cs(arg: JSMarshalerArgument, value: Span, sig?: JSMarshalerType): void { + mono_assert(!!sig, "Expected valid sig paramater"); + mono_assert(!value.isDisposed, "ObjectDisposedException"); + checkViewType(sig, value._viewType); + + set_arg_type(arg, MarshalerType.Span); + set_arg_intptr(arg, value._pointer); + set_arg_length(arg, value.length); +} + +// this only supports round-trip +function _marshal_array_segment_to_cs(arg: JSMarshalerArgument, value: ArraySegment, sig?: JSMarshalerType): void { + mono_assert(!!sig, "Expected valid sig paramater"); + const gc_handle = assert_not_disposed(value); + mono_assert(gc_handle, "Only roundtrip of ArraySegment instance created by C#"); + checkViewType(sig, value._viewType); + set_arg_type(arg, MarshalerType.ArraySegment); + set_arg_intptr(arg, value._pointer); + set_arg_length(arg, value.length); + set_gc_handle(arg, gc_handle); +} + +function checkViewType(sig: JSMarshalerType, viewType: MemoryViewType) { + const element_type = get_signature_arg1_type(sig); + if (element_type == MarshalerType.Byte) { + mono_assert(MemoryViewType.Byte == viewType, "Expected MemoryViewType.Byte"); + } + else if (element_type == MarshalerType.Int32) { + mono_assert(MemoryViewType.Int32 == viewType, "Expected MemoryViewType.Int32"); + } + else if (element_type == MarshalerType.Double) { + mono_assert(MemoryViewType.Double == viewType, "Expected MemoryViewType.Double"); + } + else { + throw new Error(`NotImplementedException ${MarshalerType[element_type]} `); + } +} + diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts new file mode 100644 index 00000000000..47d240f1f13 --- /dev/null +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -0,0 +1,581 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { PromiseControl, promise_control_symbol, create_cancelable_promise } from "./cancelable-promise"; +import cwraps from "./cwraps"; +import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy } from "./gc-handles"; +import { Module, runtimeHelpers } from "./imports"; +import { + ManagedObject, JSMarshalerArgument, ManagedError, JSMarshalerArguments, MarshalerToCs, MarshalerToJs, JSMarshalerType, + get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32, + get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type, + get_signature_arg2_type, get_signature_arg1_type, get_signature_type, cs_to_js_marshalers, js_to_cs_marshalers, + get_signature_res_type, JavaScriptMarshalerArgSize, set_gc_handle, is_args_exception, get_arg_u16, array_element_size, get_string_root, ArraySegment, Span, MemoryViewType, get_signature_arg3_type, MarshalerType, get_arg_i64_big, get_arg_intptr, get_arg_element_type +} from "./marshal"; +import { conv_string, conv_string_root } from "./strings"; +import { mono_assert, JSHandleNull, GCHandleNull } from "./types"; +import { TypedArray } from "./types/emscripten"; + +export function initialize_marshalers_to_js(): void { + if (cs_to_js_marshalers.size == 0) { + cs_to_js_marshalers.set(MarshalerType.Array, _marshal_array_to_js); + cs_to_js_marshalers.set(MarshalerType.Span, _marshal_span_to_js); + cs_to_js_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_js); + cs_to_js_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_js); + cs_to_js_marshalers.set(MarshalerType.Byte, _marshal_byte_to_js); + cs_to_js_marshalers.set(MarshalerType.Char, _marshal_char_to_js); + cs_to_js_marshalers.set(MarshalerType.Int16, _marshal_int16_to_js); + cs_to_js_marshalers.set(MarshalerType.Int32, _marshal_int32_to_js); + cs_to_js_marshalers.set(MarshalerType.Int52, _marshal_int52_to_js); + cs_to_js_marshalers.set(MarshalerType.BigInt64, _marshal_bigint64_to_js); + cs_to_js_marshalers.set(MarshalerType.Single, _marshal_float_to_js); + cs_to_js_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_js); + cs_to_js_marshalers.set(MarshalerType.Double, _marshal_double_to_js); + cs_to_js_marshalers.set(MarshalerType.String, _marshal_string_to_js); + cs_to_js_marshalers.set(MarshalerType.Exception, marshal_exception_to_js); + cs_to_js_marshalers.set(MarshalerType.JSException, marshal_exception_to_js); + cs_to_js_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_js); + cs_to_js_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_js); + cs_to_js_marshalers.set(MarshalerType.DateTime, _marshal_datetime_to_js); + cs_to_js_marshalers.set(MarshalerType.DateTimeOffset, _marshal_datetime_to_js); + cs_to_js_marshalers.set(MarshalerType.Task, _marshal_task_to_js); + cs_to_js_marshalers.set(MarshalerType.Action, _marshal_delegate_to_js); + cs_to_js_marshalers.set(MarshalerType.Function, _marshal_delegate_to_js); + cs_to_js_marshalers.set(MarshalerType.None, _marshal_null_to_js); + cs_to_js_marshalers.set(MarshalerType.Void, _marshal_null_to_js); + cs_to_js_marshalers.set(MarshalerType.Discard, _marshal_null_to_js); + } +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function generate_arg_marshal_to_js(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): { + converters: string, + call_body: string, + marshaler_type: MarshalerType +} { + let converters = ""; + let converter_types = ""; + let call_body = ""; + const converter_name = "converter" + index; + let converter_name_arg1 = "null"; + let converter_name_arg2 = "null"; + let converter_name_arg3 = "null"; + let converter_name_res = "null"; + + let marshaler_type = get_signature_type(sig); + if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { + return { + converters, + call_body, + marshaler_type + }; + } + + const marshaler_type_res = get_signature_res_type(sig); + if (marshaler_type_res !== MarshalerType.None) { + const converter = cs_to_js_marshalers.get(marshaler_type_res); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`); + + if (marshaler_type != MarshalerType.Nullable) { + converter_name_res = "converter" + index + "_res"; + converters += ", " + converter_name_res; + converter_types += " " + MarshalerType[marshaler_type_res]; + closure[converter_name_res] = converter; + } else { + marshaler_type = marshaler_type_res; + } + } + + const marshaler_type_arg1 = get_signature_arg1_type(sig); + if (marshaler_type_arg1 !== MarshalerType.None) { + const converter = js_to_cs_marshalers.get(marshaler_type_arg1); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`); + + converter_name_arg1 = "converter" + index + "_arg1"; + converters += ", " + converter_name_arg1; + converter_types += " " + MarshalerType[marshaler_type_arg1]; + closure[converter_name_arg1] = converter; + } + + const marshaler_type_arg2 = get_signature_arg2_type(sig); + if (marshaler_type_arg2 !== MarshalerType.None) { + const converter = js_to_cs_marshalers.get(marshaler_type_arg2); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`); + + converter_name_arg2 = "converter" + index + "_arg2"; + converters += ", " + converter_name_arg2; + converter_types += " " + MarshalerType[marshaler_type_arg2]; + closure[converter_name_arg2] = converter; + } + + const marshaler_type_arg3 = get_signature_arg3_type(sig); + if (marshaler_type_arg3 !== MarshalerType.None) { + const converter = js_to_cs_marshalers.get(marshaler_type_arg3); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`); + + converter_name_arg3 = "converter" + index + "_arg3"; + converters += ", " + converter_name_arg3; + converter_types += " " + MarshalerType[marshaler_type_arg3]; + closure[converter_name_arg3] = converter; + } + + const converter = cs_to_js_marshalers.get(marshaler_type); + mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type} at ${index} `); + + converters += ", " + converter_name; + converter_types += " " + MarshalerType[marshaler_type]; + closure[converter_name] = converter; + + if (marshaler_type == MarshalerType.Task) { + call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`; + } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) { + call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg3}); // ${converter_types} \n`; + } else { + call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}); // ${converter_types} \n`; + } + + return { + converters, + call_body, + marshaler_type + }; +} + +function _marshal_bool_to_js(arg: JSMarshalerArgument): boolean | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_b8(arg); +} + +function _marshal_byte_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_u8(arg); +} + +function _marshal_char_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_u16(arg); +} + +function _marshal_int16_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_i16(arg); +} + +function _marshal_int32_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_i32(arg); +} + +function _marshal_int52_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_i52(arg); +} + +function _marshal_bigint64_to_js(arg: JSMarshalerArgument): bigint | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_i64_big(arg); +} + +function _marshal_float_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_f32(arg); +} + +function _marshal_double_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_f64(arg); +} + +function _marshal_intptr_to_js(arg: JSMarshalerArgument): number | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + return get_arg_intptr(arg); +} + +function _marshal_null_to_js(): null { + return null; +} + +function _marshal_datetime_to_js(arg: JSMarshalerArgument): Date | null { + const type = get_arg_type(arg); + if (type === MarshalerType.None) { + return null; + } + return get_arg_date(arg); +} + +function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): Function | null { + const type = get_arg_type(arg); + if (type === MarshalerType.None) { + return null; + } + + const anyModule = Module as any; + const gc_handle = get_arg_gc_handle(arg); + let result = _lookup_js_owned_object(gc_handle); + if (result === null || result === undefined) { + // this will create new Function for the C# delegate + result = (arg1_js: any, arg2_js: any, arg3_js: any) => { + + const sp = anyModule.stackSave(); + try { + const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 5); + const exc = get_arg(args, 0); + set_arg_type(exc, MarshalerType.None); + const res = get_arg(args, 1); + set_arg_type(res, MarshalerType.None); + set_gc_handle(res, <any>gc_handle); + const arg1 = get_arg(args, 2); + const arg2 = get_arg(args, 3); + const arg3 = get_arg(args, 4); + + if (arg1_converter) { + arg1_converter(arg1, arg1_js); + } + if (arg2_converter) { + arg2_converter(arg2, arg2_js); + } + if (arg3_converter) { + arg3_converter(arg3, arg3_js); + } + + const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.call_delegate, args); + if (fail) throw new Error("ERR23: Unexpected error: " + conv_string(fail)); + if (is_args_exception(args)) throw marshal_exception_to_js(exc); + + if (res_converter) { + return res_converter(res); + } + + } finally { + anyModule.stackRestore(sp); + } + }; + + setup_managed_proxy(result, gc_handle); + } + + return result; +} + +function _marshal_task_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType, res_converter?: MarshalerToJs): Promise<any> | null { + const type = get_arg_type(arg); + if (type === MarshalerType.None) { + return null; + } + + if (type !== MarshalerType.Task) { + + if (!res_converter) { + // when we arrived here from _marshal_cs_object_to_js + res_converter = cs_to_js_marshalers.get(type); + } + mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]} `); + + // this is already resolved + const val = res_converter(arg); + return new Promise((resolve) => resolve(val)); + } + + const js_handle = get_arg_js_handle(arg); + // console.log("_marshal_task_to_js A" + js_handle); + if (js_handle == JSHandleNull) { + // this is already resolved void + return new Promise((resolve) => resolve(undefined)); + } + const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); + mono_assert(!!promise, () => `ERR28: promise not found for js_handle: ${js_handle} `); + const promise_control = promise[promise_control_symbol] as PromiseControl; + mono_assert(!!promise_control, () => `ERR27: promise_control not found for js_handle: ${js_handle} `); + + const orig_resolve = promise_control.resolve; + promise_control.resolve = (argInner: JSMarshalerArgument) => { + // console.log("_marshal_task_to_js R" + js_handle); + const type = get_arg_type(argInner); + if (type === MarshalerType.None) { + orig_resolve(null); + return; + } + + if (!res_converter) { + // when we arrived here from _marshal_cs_object_to_js + res_converter = cs_to_js_marshalers.get(type); + } + mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]}`); + + const js_value = res_converter!(argInner); + orig_resolve(js_value); + }; + + return promise; +} + +export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { + const exc = get_arg(args, 0); + const res = get_arg(args, 1); + const arg_handle = get_arg(args, 2); + const arg_value = get_arg(args, 3); + + const exc_type = get_arg_type(exc); + const value_type = get_arg_type(arg_value); + const js_handle = get_arg_js_handle(arg_handle); + + if (js_handle === JSHandleNull) { + const { promise, promise_control } = create_cancelable_promise(); + const js_handle = mono_wasm_get_js_handle(promise)!; + set_js_handle(res, js_handle); + + if (exc_type !== MarshalerType.None) { + // this is already failed task + const reason = marshal_exception_to_js(exc); + promise_control.reject(reason); + } + else if (value_type !== MarshalerType.Task) { + // this is already resolved task + const sub_converter = cs_to_js_marshalers.get(value_type); + mono_assert(sub_converter, () => `Unknow sub_converter for type ${MarshalerType[value_type]} `); + const data = sub_converter(arg_value); + promise_control.resolve(data); + } + } else { + // resolve existing promise + const promise = mono_wasm_get_jsobj_from_js_handle(js_handle); + mono_assert(!!promise, () => `ERR25: promise not found for js_handle: ${js_handle} `); + const promise_control = promise[promise_control_symbol] as PromiseControl; + mono_assert(!!promise_control, () => `ERR26: promise_control not found for js_handle: ${js_handle} `); + + if (exc_type !== MarshalerType.None) { + const reason = marshal_exception_to_js(exc); + promise_control.reject(reason); + } + else if (value_type !== MarshalerType.Task) { + // here we assume that resolve was wrapped with sub_converter inside _marshal_task_to_js + promise_control.resolve(arg_value); + } + } + set_arg_type(res, MarshalerType.Task); + set_arg_type(exc, MarshalerType.None); +} + +function _marshal_string_to_js(arg: JSMarshalerArgument): string | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + const root = get_string_root(arg); + try { + const value = conv_string_root(root); + return value; + } finally { + root.release(); + } +} + +export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + if (type == MarshalerType.JSException) { + // this is JSException roundtrip + const js_handle = get_arg_js_handle(arg); + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + return js_obj; + } + + const gc_handle = get_arg_gc_handle(arg); + let result = _lookup_js_owned_object(gc_handle); + if (result === null || result === undefined) { + // this will create new ManagedError + const message = _marshal_string_to_js(arg); + result = new ManagedError(message!); + + setup_managed_proxy(result, gc_handle); + } + + return result; +} + +function _marshal_js_object_to_js(arg: JSMarshalerArgument): any { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + const js_handle = get_arg_js_handle(arg); + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + return js_obj; +} + +function _marshal_cs_object_to_js(arg: JSMarshalerArgument): any { + const marshaler_type = get_arg_type(arg); + if (marshaler_type == MarshalerType.None) { + return null; + } + if (marshaler_type == MarshalerType.JSObject) { + const js_handle = get_arg_js_handle(arg); + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + return js_obj; + } + + if (marshaler_type == MarshalerType.Array) { + const element_type = get_arg_element_type(arg); + return _marshal_array_to_js_impl(arg, element_type); + } + + if (marshaler_type == MarshalerType.Object) { + const gc_handle = get_arg_gc_handle(arg); + if (gc_handle === GCHandleNull) { + return null; + } + + // see if we have js owned instance for this gc_handle already + let result = _lookup_js_owned_object(gc_handle); + + // If the JS object for this gc_handle was already collected (or was never created) + if (!result) { + result = new ManagedObject(); + setup_managed_proxy(result, gc_handle); + } + + return result; + } + + // other types + const converter = cs_to_js_marshalers.get(marshaler_type); + mono_assert(converter, () => `Unknow converter for type ${MarshalerType[marshaler_type]}`); + return converter(arg); +} + +function _marshal_array_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): Array<any> | TypedArray | null { + mono_assert(!!sig, "Expected valid sig paramater"); + const element_type = get_signature_arg1_type(sig); + return _marshal_array_to_js_impl(arg, element_type); +} + +function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: MarshalerType): Array<any> | TypedArray | null { + const type = get_arg_type(arg); + if (type == MarshalerType.None) { + return null; + } + const elementSize = array_element_size(element_type); + mono_assert(elementSize != -1, () => `Element type ${MarshalerType[element_type]} not supported`); + const buffer_ptr = get_arg_intptr(arg); + const length = get_arg_length(arg); + let result: Array<any> | TypedArray | null = null; + if (element_type == MarshalerType.String) { + result = new Array(length); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(<any>buffer_ptr, index); + result[index] = _marshal_string_to_js(element_arg); + } + cwraps.mono_wasm_deregister_root(<any>buffer_ptr); + } + else if (element_type == MarshalerType.Object) { + result = new Array(length); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(<any>buffer_ptr, index); + result[index] = _marshal_cs_object_to_js(element_arg); + } + cwraps.mono_wasm_deregister_root(<any>buffer_ptr); + } + else if (element_type == MarshalerType.JSObject) { + result = new Array(length); + for (let index = 0; index < length; index++) { + const element_arg = get_arg(<any>buffer_ptr, index); + result[index] = _marshal_js_object_to_js(element_arg); + } + } + else if (element_type == MarshalerType.Byte) { + const sourceView = Module.HEAPU8.subarray(<any>buffer_ptr, buffer_ptr + length); + result = sourceView.slice();//copy + } + else if (element_type == MarshalerType.Int32) { + const sourceView = Module.HEAP32.subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); + result = sourceView.slice();//copy + } + else if (element_type == MarshalerType.Double) { + const sourceView = Module.HEAPF64.subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); + result = sourceView.slice();//copy + } + else { + throw new Error(`NotImplementedException ${MarshalerType[element_type]} `); + } + Module._free(<any>buffer_ptr); + return result; +} + +function _marshal_span_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): Span { + mono_assert(!!sig, "Expected valid sig paramater"); + + const element_type = get_signature_arg1_type(sig); + const buffer_ptr = get_arg_intptr(arg); + const length = get_arg_length(arg); + let result: Span | null = null; + if (element_type == MarshalerType.Byte) { + result = new Span(<any>buffer_ptr, length, MemoryViewType.Byte); + } + else if (element_type == MarshalerType.Int32) { + result = new Span(<any>buffer_ptr, length, MemoryViewType.Int32); + } + else if (element_type == MarshalerType.Double) { + result = new Span(<any>buffer_ptr, length, MemoryViewType.Double); + } + else { + throw new Error(`NotImplementedException ${MarshalerType[element_type]} `); + } + return result; +} + +function _marshal_array_segment_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): ArraySegment { + mono_assert(!!sig, "Expected valid sig paramater"); + + const element_type = get_signature_arg1_type(sig); + const buffer_ptr = get_arg_intptr(arg); + const length = get_arg_length(arg); + let result: ArraySegment | null = null; + if (element_type == MarshalerType.Byte) { + result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Byte); + } + else if (element_type == MarshalerType.Int32) { + result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Int32); + } + else if (element_type == MarshalerType.Double) { + result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Double); + } + else { + throw new Error(`NotImplementedException ${MarshalerType[element_type]} `); + } + const gc_handle = get_arg_gc_handle(arg); + setup_managed_proxy(result, gc_handle); + + return result; +} diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts new file mode 100644 index 00000000000..9a5e6a6dd63 --- /dev/null +++ b/src/mono/wasm/runtime/marshal.ts @@ -0,0 +1,509 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; +import { Module } from "./imports"; +import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory"; +import { mono_wasm_new_external_root, WasmRoot } from "./roots"; +import { mono_assert, GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull } from "./types"; +import { CharPtr, NativePointer, TypedArray, VoidPtr } from "./types/emscripten"; + +export const cs_to_js_marshalers = new Map<MarshalerType, MarshalerToJs>(); +export const js_to_cs_marshalers = new Map<MarshalerType, MarshalerToCs>(); +export const bound_cs_function_symbol = Symbol.for("wasm bound_cs_function"); +export const bound_js_function_symbol = Symbol.for("wasm bound_js_function"); + +/** + * JSFunctionSignature is pointer to [ + * Version: number, + * ArgumentCount: number, + * exc: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * res: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * arg1: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * arg2: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType} + * ... + * ] + * + * Layout of the call stack frame buffers is array of JSMarshalerArgument + * JSMarshalerArguments is pointer to [ + * exc: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * res: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * arg1: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * arg2: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* }, + * ... + * ] + */ + + +export const JavaScriptMarshalerArgSize = 16; +export const JSMarshalerTypeSize = 32; +export const JSMarshalerSignatureHeaderSize = 4 + 4; // without Exception and Result + +export interface JSMarshalerArguments extends NativePointer { + __brand: "JSMarshalerArguments" +} + +export interface JSFunctionSignature extends NativePointer { + __brand: "JSFunctionSignatures" +} + +export interface JSMarshalerType extends NativePointer { + __brand: "JSMarshalerType" +} + +export interface JSMarshalerArgument extends NativePointer { + __brand: "JSMarshalerArgument" +} + +export function get_arg(args: JSMarshalerArguments, index: number): JSMarshalerArgument { + mono_assert(args, "Null args"); + return <any>args + (index * JavaScriptMarshalerArgSize); +} + +export function is_args_exception(args: JSMarshalerArguments): boolean { + mono_assert(args, "Null args"); + const exceptionType = get_arg_type(<any>args); + return exceptionType !== MarshalerType.None; +} + +export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType { + mono_assert(signature, "Null signatures"); + return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize; +} + +export function get_signature_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null sig"); + return <any>getU32(sig); +} + +export function get_signature_res_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 16); +} + +export function get_signature_custom_code(sig: JSMarshalerType): CharPtr { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 8); +} + +export function get_signature_custom_code_len(sig: JSMarshalerType): number { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 12); +} + +export function get_signature_arg1_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 20); +} + +export function get_signature_arg2_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 24); +} + +export function get_signature_arg3_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null sig"); + return <any>getU32(<any>sig + 28); +} + +export function get_signature_argument_count(signature: JSFunctionSignature): number { + mono_assert(signature, "Null signatures"); + return <any>getI32(<any>signature + 4); +} + +export function get_signature_version(signature: JSFunctionSignature): number { + mono_assert(signature, "Null signatures"); + return <any>getI32(signature); +} + +export function get_sig_type(sig: JSMarshalerType): MarshalerType { + mono_assert(sig, "Null signatures"); + return <any>getU32(sig); +} + +export function get_arg_type(arg: JSMarshalerArgument): MarshalerType { + mono_assert(arg, "Null arg"); + const type = getU32(<any>arg + 12); + return <any>type; +} + +export function get_arg_element_type(arg: JSMarshalerArgument): MarshalerType { + mono_assert(arg, "Null arg"); + const type = getU32(<any>arg + 4); + return <any>type; +} + +export function set_arg_type(arg: JSMarshalerArgument, type: MarshalerType): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg + 12, type); +} + +export function set_arg_element_type(arg: JSMarshalerArgument, type: MarshalerType): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg + 4, type); +} + +export function get_arg_b8(arg: JSMarshalerArgument): boolean { + mono_assert(arg, "Null arg"); + return !!getU8(<any>arg); +} + +export function get_arg_u8(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getU8(<any>arg); +} + +export function get_arg_u16(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getU16(<any>arg); +} + +export function get_arg_i16(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getI16(<any>arg); +} + +export function get_arg_i32(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getI32(<any>arg); +} + +export function get_arg_intptr(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getU32(<any>arg); +} + +export function get_arg_i52(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + // we know that the range check and conversion from Int64 was be done on C# side + return getF64(<any>arg); +} + +export function get_arg_i64_big(arg: JSMarshalerArgument): bigint { + mono_assert(arg, "Null arg"); + return getI64Big(<any>arg); +} + +export function get_arg_date(arg: JSMarshalerArgument): Date { + mono_assert(arg, "Null arg"); + const unixTime = getF64(<any>arg); + const date = new Date(unixTime); + return date; +} + +export function get_arg_f32(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getF32(<any>arg); +} + +export function get_arg_f64(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return getF64(<any>arg); +} + +export function set_arg_b8(arg: JSMarshalerArgument, value: boolean): void { + mono_assert(arg, "Null arg"); + mono_assert(typeof value === "boolean", () => `Value is not a Boolean: ${value} (${typeof (value)})`); + setU8(<any>arg, value ? 1 : 0); +} + +export function set_arg_u8(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setU8(<any>arg, value); +} + +export function set_arg_u16(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setU16(<any>arg, value); +} + +export function set_arg_i16(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setI16(<any>arg, value); +} + +export function set_arg_i32(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setI32(<any>arg, value); +} + +export function set_arg_intptr(arg: JSMarshalerArgument, value: VoidPtr): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg, <any>value); +} + +export function set_arg_i52(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + mono_assert(Number.isSafeInteger(value), () => `Value is not an integer: ${value} (${typeof (value)})`); + // we know that conversion to Int64 would be done on C# side + setF64(<any>arg, value); +} + +export function set_arg_i64_big(arg: JSMarshalerArgument, value: bigint): void { + mono_assert(arg, "Null arg"); + setI64Big(<any>arg, value); +} + +export function set_arg_date(arg: JSMarshalerArgument, value: Date): void { + mono_assert(arg, "Null arg"); + // getTime() is always UTC + const unixTime = value.getTime(); + setF64(<any>arg, unixTime); +} + +export function set_arg_f64(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setF64(<any>arg, value); +} + +export function set_arg_f32(arg: JSMarshalerArgument, value: number): void { + mono_assert(arg, "Null arg"); + setF32(<any>arg, value); +} + +export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle { + mono_assert(arg, "Null arg"); + return <any>getU32(<any>arg + 4); +} + +export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg + 4, <any>jsHandle); +} + +export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle { + mono_assert(arg, "Null arg"); + return <any>getU32(<any>arg + 4); +} + +export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg + 4, <any>gcHandle); +} + +export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> { + mono_assert(arg, "Null arg"); + return mono_wasm_new_external_root<MonoString>(<any>arg); +} + +export function get_arg_length(arg: JSMarshalerArgument): number { + mono_assert(arg, "Null arg"); + return <any>getI32(<any>arg + 8); +} + +export function set_arg_length(arg: JSMarshalerArgument, size: number): void { + mono_assert(arg, "Null arg"); + setI32(<any>arg + 8, size); +} + +export function set_root(arg: JSMarshalerArgument, root: WasmRoot<MonoObject>): void { + mono_assert(arg, "Null arg"); + setU32(<any>arg + 0, root.get_address()); +} + +export interface IDisposable { + dispose(): void; + get isDisposed(): boolean; +} + +export class ManagedObject implements IDisposable { + dispose(): void { + teardown_managed_proxy(this, GCHandleNull); + } + + get isDisposed(): boolean { + return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull; + } + + toString(): string { + return `CsObject(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`; + } +} + +export class ManagedError extends Error implements IDisposable { + constructor(message: string) { + super(message); + } + + get stack(): string | undefined { + //todo implement lazy managed stack strace from this[js_owned_gc_handle_symbol]! + return super.stack; + } + + dispose(): void { + teardown_managed_proxy(this, GCHandleNull); + } + + get isDisposed(): boolean { + return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull; + } + + toString(): string { + return `ManagedError(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`; + } +} + +export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs) => any; +export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, sig?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs) => void; + +export function get_signature_marshaler(signature: JSFunctionSignature, index: number): JSHandle { + mono_assert(signature, "Null signatures"); + const sig = get_sig(signature, index); + return <any>getU32(<any>sig + 8); +} + + +export function array_element_size(element_type: MarshalerType): number { + return element_type == MarshalerType.Byte ? 1 + : element_type == MarshalerType.Int32 ? 4 + : element_type == MarshalerType.Int52 ? 8 + : element_type == MarshalerType.Double ? 8 + : element_type == MarshalerType.String ? JavaScriptMarshalerArgSize + : element_type == MarshalerType.Object ? JavaScriptMarshalerArgSize + : element_type == MarshalerType.JSObject ? JavaScriptMarshalerArgSize + : -1; +} + +export enum MemoryViewType { + Byte, + Int32, + Double, +} + +abstract class MemoryView implements IMemoryView, IDisposable { + protected constructor(public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) { + } + + abstract dispose(): void; + abstract get isDisposed(): boolean; + + _unsafe_create_view(): TypedArray { + // this view must be short lived so that it doesn't fail after wasm memory growth + // for that reason we also don't give the view out to end user and provide set/slice/copyTo API instead + const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(Module.HEAPU8.buffer, <any>this._pointer, this._length) + : this._viewType == MemoryViewType.Int32 ? new Int32Array(Module.HEAP32.buffer, <any>this._pointer, this._length) + : this._viewType == MemoryViewType.Double ? new Float64Array(Module.HEAPF64.buffer, <any>this._pointer, this._length) + : null; + if (!view) throw new Error("NotImplementedException"); + return view; + } + + set(source: TypedArray, targetOffset?: number): void { + mono_assert(!this.isDisposed, "ObjectDisposedException"); + const targetView = this._unsafe_create_view(); + mono_assert(source && targetView && source.constructor === targetView.constructor, () => `Expected ${targetView.constructor}`); + targetView.set(source, targetOffset); + // TODO consider memory write barrier + } + + copyTo(target: TypedArray, sourceOffset?: number): void { + mono_assert(!this.isDisposed, "ObjectDisposedException"); + const sourceView = this._unsafe_create_view(); + mono_assert(target && sourceView && target.constructor === sourceView.constructor, () => `Expected ${sourceView.constructor}`); + const trimmedSource = sourceView.subarray(sourceOffset); + // TODO consider memory read barrier + target.set(trimmedSource); + } + + slice(start?: number, end?: number): TypedArray { + mono_assert(!this.isDisposed, "ObjectDisposedException"); + const sourceView = this._unsafe_create_view(); + // TODO consider memory read barrier + return sourceView.slice(start, end); + } + + get length(): number { + mono_assert(!this.isDisposed, "ObjectDisposedException"); + return this._length; + } + + get byteLength(): number { + mono_assert(!this.isDisposed, "ObjectDisposedException"); + return this._viewType == MemoryViewType.Byte ? this._length + : this._viewType == MemoryViewType.Int32 ? this._length << 2 + : this._viewType == MemoryViewType.Double ? this._length << 3 + : 0; + } +} + +export interface IMemoryView { + /** + * copies elements from provided source to the wasm memory. + * target has to have the elements of the same type as the underlying C# array. + * same as TypedArray.set() + */ + set(source: TypedArray, targetOffset?: number): void; + /** + * copies elements from wasm memory to provided target. + * target has to have the elements of the same type as the underlying C# array. + */ + copyTo(target: TypedArray, sourceOffset?: number): void; + /** + * same as TypedArray.slice() + */ + slice(start?: number, end?: number): TypedArray; + + get length(): number; + get byteLength(): number; +} + +export class Span extends MemoryView implements IDisposable { + private is_disposed = false; + public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) { + super(pointer, length, viewType); + } + dispose(): void { + this.is_disposed = true; + } + get isDisposed(): boolean { + return this.is_disposed; + } +} + +export class ArraySegment extends MemoryView { + public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) { + super(pointer, length, viewType); + } + + dispose(): void { + teardown_managed_proxy(this, GCHandleNull); + } + + get isDisposed(): boolean { + return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull; + } +} + +// please keep in sync with src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\MarshalerType.cs +export enum MarshalerType { + None = 0, + Void = 1, + Discard, + Boolean, + Byte, + Char, + Int16, + Int32, + Int52, + BigInt64, + Double, + Single, + IntPtr, + JSObject, + Object, + String, + Exception, + DateTime, + DateTimeOffset, + + Nullable, + Task, + Array, + ArraySegment, + Span, + Action, + Function, + + // only on runtime + JSException, +}
\ No newline at end of file diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 776a8eef168..3b3ec8f1486 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -16,6 +16,8 @@ import { find_corlib_class } from "./class-loader"; import { VoidPtr, CharPtr } from "./types/emscripten"; import { DotnetPublicAPI } from "./exports"; import { mono_on_abort } from "./run"; +import { initialize_marshalers_to_cs } from "./marshal-to-cs"; +import { initialize_marshalers_to_js } from "./marshal-to-js"; import { mono_wasm_new_root } from "./roots"; import { init_crypto } from "./crypto-worker"; import { init_polyfills } from "./polyfills"; @@ -452,6 +454,21 @@ export function bindings_lazy_init(): void { if (!runtimeHelpers.get_call_sig_ref) throw "Can't find GetCallSignatureRef method"; + runtimeHelpers.complete_task_method = get_method("CompleteTask"); + if (!runtimeHelpers.complete_task_method) + throw "Can't find CompleteTask method"; + + runtimeHelpers.create_task_method = get_method("CreateTaskCallback"); + if (!runtimeHelpers.create_task_method) + throw "Can't find CreateTaskCallback method"; + + runtimeHelpers.call_delegate = get_method("CallDelegate"); + if (!runtimeHelpers.call_delegate) + throw "Can't find CallDelegate method"; + + initialize_marshalers_to_js(); + initialize_marshalers_to_cs(); + _create_primitive_converters(); runtimeHelpers._box_root = mono_wasm_new_root<MonoObject>(); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 649de856d70..8e3970722aa 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -132,6 +132,9 @@ export const enum AssetBehaviours { export type RuntimeHelpers = { get_call_sig_ref: MonoMethod; + complete_task_method: MonoMethod; + create_task_method: MonoMethod; + call_delegate: MonoMethod; runtime_interop_namespace: string; runtime_interop_exports_classname: string; runtime_interop_exports_class: MonoClass; diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index caaa9bd3930..feeebf0f449 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -1,19 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_new_root, mono_wasm_new_external_root } from "./roots"; -import { setI32 } from "./memory"; import { prevent_timer_throttling } from "./scheduling"; import { Queue } from "./queue"; -import { PromiseControl, _create_cancelable_promise } from "./cancelable-promise"; -import { mono_array_root_to_js_array, _wrap_delegate_root_as_function } from "./cs-to-js"; -import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./gc-handles"; -import { _wrap_js_thenable_as_task_root } from "./js-to-cs"; -import { wrap_error_root } from "./method-calls"; -import { conv_string_root } from "./strings"; -import { JSHandle, MonoArray, MonoObject, MonoString, MonoObjectRef } from "./types"; -import { Module } from "./imports"; -import { Int32Ptr, VoidPtr } from "./types/emscripten"; +import { PromiseControl, create_cancelable_promise } from "./cancelable-promise"; +import { mono_assert } from "./types"; +import { ArraySegment, IDisposable } from "./marshal"; const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer"); const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset"); @@ -30,302 +22,210 @@ let _text_encoder_utf8: TextEncoder | undefined = undefined; const ws_send_buffer_blocking_threshold = 65536; const emptyBuffer = new Uint8Array(); -export function mono_wasm_web_socket_open_ref(uri_address: MonoObjectRef, subProtocols: MonoObjectRef, on_close: MonoObjectRef, web_socket_js_handle: Int32Ptr, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); - const uri_root = mono_wasm_new_external_root<MonoString>(uri_address); - const sub_root = mono_wasm_new_external_root<MonoArray>(subProtocols); - const on_close_root = mono_wasm_new_root(); - on_close_root.copy_from_address(on_close); - try { - const js_uri = conv_string_root(uri_root); - if (!js_uri) { - wrap_error_root(is_exception, "ERR12: Invalid uri '" + uri_root.value + "'", result_root); - return; - } +export function ws_wasm_create(uri: string, sub_protocols: string[] | null, onClosed: (code: number, reason: string) => void): WebSocketExtension { + mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`); + + const ws = new globalThis.WebSocket(uri, sub_protocols || undefined) as WebSocketExtension; + const { promise_control: open_promise_control } = create_cancelable_promise(); + + ws[wasm_ws_pending_receive_event_queue] = new Queue(); + ws[wasm_ws_pending_receive_promise_queue] = new Queue(); + ws[wasm_ws_pending_open_promise] = open_promise_control; + ws[wasm_ws_pending_send_promises] = []; + ws[wasm_ws_pending_close_promises] = []; + ws.binaryType = "arraybuffer"; + const local_on_open = () => { + if (ws[wasm_ws_is_aborted]) return; + open_promise_control.resolve(ws); + prevent_timer_throttling(); + }; + const local_on_message = (ev: MessageEvent) => { + if (ws[wasm_ws_is_aborted]) return; + _mono_wasm_web_socket_on_message(ws, ev); + prevent_timer_throttling(); + }; + const local_on_close = (ev: CloseEvent) => { + ws.removeEventListener("message", local_on_message); + if (ws[wasm_ws_is_aborted]) return; + if (onClosed) onClosed(ev.code, ev.reason); + + // this reject would not do anything if there was already "open" before it. + open_promise_control.reject(ev.reason); - const js_subs = mono_array_root_to_js_array(sub_root); - - const js_on_close = _wrap_delegate_root_as_function(on_close_root)!; - - const ws = new globalThis.WebSocket(js_uri, <any>js_subs) as WebSocketExtension; - const { promise, promise_control: open_promise_control } = _create_cancelable_promise(); - - ws[wasm_ws_pending_receive_event_queue] = new Queue(); - ws[wasm_ws_pending_receive_promise_queue] = new Queue(); - ws[wasm_ws_pending_open_promise] = open_promise_control; - ws[wasm_ws_pending_send_promises] = []; - ws[wasm_ws_pending_close_promises] = []; - ws.binaryType = "arraybuffer"; - const local_on_open = () => { - if (ws[wasm_ws_is_aborted]) return; - open_promise_control.resolve(null); - prevent_timer_throttling(); - }; - const local_on_message = (ev: MessageEvent) => { - if (ws[wasm_ws_is_aborted]) return; - _mono_wasm_web_socket_on_message(ws, ev); - prevent_timer_throttling(); - }; - const local_on_close = (ev: CloseEvent) => { - ws.removeEventListener("message", local_on_message); - if (ws[wasm_ws_is_aborted]) return; - js_on_close(ev.code, ev.reason); - - // this reject would not do anything if there was already "open" before it. - open_promise_control.reject(ev.reason); - - for (const close_promise_control of ws[wasm_ws_pending_close_promises]) { - close_promise_control.resolve(); - } + for (const close_promise_control of ws[wasm_ws_pending_close_promises]) { + close_promise_control.resolve(); + } - // send close to any pending receivers, to wake them - const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue]; - receive_promise_queue.drain((receive_promise_control) => { - const response_ptr = receive_promise_control.response_ptr; - setI32(<any>response_ptr + 0, 0);// count - setI32(<any>response_ptr + 4, 2);// type:close - setI32(<any>response_ptr + 8, 1);// end_of_message: true - receive_promise_control.resolve(null); - }); - }; - const local_on_error = (ev: any) => { - open_promise_control.reject(ev.message); - }; - ws.addEventListener("message", local_on_message); - ws.addEventListener("open", local_on_open, { once: true }); - ws.addEventListener("close", local_on_close, { once: true }); - ws.addEventListener("error", local_on_error, { once: true }); - const ws_js_handle = mono_wasm_get_js_handle(ws); - Module.setValue(web_socket_js_handle, <any>ws_js_handle, "i32"); - - const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root); - // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC - Module.setValue(thenable_js_handle, <any>then_js_handle, "i32"); - } - catch (ex) { - wrap_error_root(is_exception, ex, result_root); - } - finally { - result_root.release(); - uri_root.release(); - sub_root.release(); - on_close_root.release(); - } + // send close to any pending receivers, to wake them + const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue]; + receive_promise_queue.drain((receive_promise_control) => { + + const response = new Int32Array([ + 0,// count + 2, // type:close + 1]);// end_of_message: true + receive_promise_control.responseView.set(response); + receive_promise_control.resolve(null); + }); + }; + const local_on_error = (ev: any) => { + open_promise_control.reject(ev.message || "WebSocket error"); + }; + ws.addEventListener("message", local_on_message); + ws.addEventListener("open", local_on_open, { once: true }); + ws.addEventListener("close", local_on_close, { once: true }); + ws.addEventListener("error", local_on_error, { once: true }); + + return ws; } -export function mono_wasm_web_socket_send(webSocket_js_handle: JSHandle, buffer_ptr: VoidPtr, offset: number, length: number, message_type: number, end_of_message: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); - try { - const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle); - if (!ws) - throw new Error("ERR17: Invalid JS object handle " + webSocket_js_handle); +export function ws_wasm_open(ws: WebSocketExtension): Promise<void> | null { + mono_assert(!!ws, "ERR17: expected ws instance"); + const open_promise_control = ws[wasm_ws_pending_open_promise]; + return open_promise_control.promise; +} - if (ws.readyState != WebSocket.OPEN) { - throw new Error("InvalidState: The WebSocket is not connected."); - } +export function ws_wasm_send(ws: WebSocketExtension, bufferView: ArraySegment, message_type: number, end_of_message: boolean): Promise<void> | null { + mono_assert(!!ws, "ERR17: expected ws instance"); - const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, buffer_ptr, offset, length, message_type, end_of_message); + const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, bufferView, message_type, end_of_message); - if (!end_of_message || !whole_buffer) { - result_root.clear(); // we are done buffering synchronously, no promise - return; - } - _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, thenable_js_handle, result_address); - } - catch (ex) { - wrap_error_root(is_exception, ex, result_root); - } - finally { - result_root.release(); + if (!end_of_message || !whole_buffer) { + return null; } + + return _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, bufferView); } -export function mono_wasm_web_socket_receive(webSocket_js_handle: JSHandle, buffer_ptr: VoidPtr, offset: number, length: number, response_ptr: VoidPtr, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); +export function ws_wasm_receive(ws: WebSocketExtension, bufferView: ArraySegment, responseView: ArraySegment): Promise<void> | null { + mono_assert(!!ws, "ERR18: expected ws instance"); - try { - const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle); - if (!ws) - throw new Error("ERR18: Invalid JS object handle " + webSocket_js_handle); - const receive_event_queue = ws[wasm_ws_pending_receive_event_queue]; - const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue]; + const receive_event_queue = ws[wasm_ws_pending_receive_event_queue]; + const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue]; - const readyState = ws.readyState; - if (readyState != WebSocket.OPEN && readyState != WebSocket.CLOSING) { - throw new Error("InvalidState: The WebSocket is not connected."); - } + const readyState = ws.readyState; + if (readyState != WebSocket.OPEN && readyState != WebSocket.CLOSING) { + throw new Error("InvalidState: The WebSocket is not connected."); + } - if (receive_event_queue.getLength()) { - if (receive_promise_queue.getLength() != 0) { - throw new Error("ERR20: Invalid WS state");// assert - } - // finish synchronously - _mono_wasm_web_socket_receive_buffering(receive_event_queue, buffer_ptr, offset, length, response_ptr); + if (receive_event_queue.getLength()) { + mono_assert(receive_promise_queue.getLength() == 0, "ERR20: Invalid WS state"); - Module.setValue(thenable_js_handle, 0, "i32"); - result_root.clear(); - return; - } - const { promise, promise_control } = _create_cancelable_promise(undefined, undefined); - const receive_promise_control = promise_control as ReceivePromiseControl; - receive_promise_control.buffer_ptr = buffer_ptr; - receive_promise_control.buffer_offset = offset; - receive_promise_control.buffer_length = length; - receive_promise_control.response_ptr = response_ptr; - receive_promise_queue.enqueue(receive_promise_control); - - const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root); - // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC - Module.setValue(thenable_js_handle, <any>then_js_handle, "i32"); - } - catch (ex) { - wrap_error_root(is_exception, ex, result_root); - } - finally { - result_root.release(); + // finish synchronously + _mono_wasm_web_socket_receive_buffering(receive_event_queue, bufferView, responseView); + + return null; } -} + const { promise, promise_control } = create_cancelable_promise(undefined, undefined); + const receive_promise_control = promise_control as ReceivePromiseControl; + receive_promise_control.bufferView = bufferView; + receive_promise_control.responseView = responseView; + receive_promise_queue.enqueue(receive_promise_control); -export function mono_wasm_web_socket_close_ref(webSocket_js_handle: JSHandle, code: number, reason: MonoObjectRef, wait_for_close_received: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); - const reason_root = mono_wasm_new_external_root<MonoString>(reason); - try { - const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle); - if (!ws) - throw new Error("ERR19: Invalid JS object handle " + webSocket_js_handle); + return promise; +} - if (ws.readyState == WebSocket.CLOSED) { - result_root.clear(); - return; - } +export function ws_wasm_close(ws: WebSocketExtension, code: number, reason: string | null, wait_for_close_received: boolean): Promise<void> | null { + mono_assert(!!ws, "ERR19: expected ws instance"); - const js_reason = conv_string_root(reason_root); - if (wait_for_close_received) { - const { promise, promise_control } = _create_cancelable_promise(); - ws[wasm_ws_pending_close_promises].push(promise_control); + if (ws.readyState == WebSocket.CLOSED) { + return null; + } - if (typeof (js_reason) === "string") { - ws.close(code, js_reason); - } else { - ws.close(code); - } + if (wait_for_close_received) { + const { promise, promise_control } = create_cancelable_promise(); + ws[wasm_ws_pending_close_promises].push(promise_control); - const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root); - // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC - Module.setValue(thenable_js_handle, <any>then_js_handle, "i32"); - } - else { - if (!mono_wasm_web_socket_close_warning) { - mono_wasm_web_socket_close_warning = true; - console.warn("WARNING: Web browsers do not support closing the output side of a WebSocket. CloseOutputAsync has closed the socket and discarded any incoming messages."); - } - if (typeof (js_reason) === "string") { - ws.close(code, js_reason); - } else { - ws.close(code); - } - Module.setValue(thenable_js_handle, 0, "i32"); - result_root.clear(); + if (typeof reason === "string") { + ws.close(code, reason); + } else { + ws.close(code); } + return promise; } - catch (ex) { - wrap_error_root(is_exception, ex, result_root); - } - finally { - result_root.release(); - reason_root.release(); - } -} - -export function mono_wasm_web_socket_abort(webSocket_js_handle: JSHandle, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); - try { - const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle) as WebSocketExtension; - if (!ws) - throw new Error("ERR18: Invalid JS object handle " + webSocket_js_handle); - - ws[wasm_ws_is_aborted] = true; - const open_promise_control = ws[wasm_ws_pending_open_promise]; - if (open_promise_control) { - open_promise_control.reject("OperationCanceledException"); - } - for (const close_promise_control of ws[wasm_ws_pending_close_promises]) { - close_promise_control.reject("OperationCanceledException"); + else { + if (!mono_wasm_web_socket_close_warning) { + mono_wasm_web_socket_close_warning = true; + console.warn("WARNING: Web browsers do not support closing the output side of a WebSocket. CloseOutputAsync has closed the socket and discarded any incoming messages."); } - for (const send_promise_control of ws[wasm_ws_pending_send_promises]) { - send_promise_control.reject("OperationCanceledException"); + if (typeof reason === "string") { + ws.close(code, reason); + } else { + ws.close(code); } + return null; + } +} - ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => { - receive_promise_control.reject("OperationCanceledException"); - }); - - // this is different from Managed implementation - ws.close(1000, "Connection was aborted."); +export function ws_wasm_abort(ws: WebSocketExtension): void { + mono_assert(!!ws, "ERR18: expected ws instance"); - result_root.clear(); + ws[wasm_ws_is_aborted] = true; + const open_promise_control = ws[wasm_ws_pending_open_promise]; + if (open_promise_control) { + open_promise_control.reject("OperationCanceledException"); } - catch (ex) { - wrap_error_root(is_exception, ex, result_root); + for (const close_promise_control of ws[wasm_ws_pending_close_promises]) { + close_promise_control.reject("OperationCanceledException"); } - finally { - result_root.release(); + for (const send_promise_control of ws[wasm_ws_pending_send_promises]) { + send_promise_control.reject("OperationCanceledException"); } + + ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => { + receive_promise_control.reject("OperationCanceledException"); + }); + + // this is different from Managed implementation + ws.close(1000, "Connection was aborted."); } -function _mono_wasm_web_socket_send_and_wait(ws: WebSocketExtension, buffer: Uint8Array | string, thenable_js_handle: Int32Ptr, result_address: MonoObjectRef): void { - const result_root = mono_wasm_new_external_root<MonoObject>(result_address); - try { - // send and return promise - ws.send(buffer); - ws[wasm_ws_pending_send_buffer] = null; - - // if the remaining send buffer is small, we don't block so that the throughput doesn't suffer. - // Otherwise we block so that we apply some backpresure to the application sending large data. - // this is different from Managed implementation - if (ws.bufferedAmount < ws_send_buffer_blocking_threshold) { - result_root.clear(); - return; - } +function _mono_wasm_web_socket_send_and_wait(ws: WebSocketExtension, buffer: Uint8Array | string, managedBuffer: IDisposable): Promise<void> | null { + // send and return promise + ws.send(buffer); + managedBuffer.dispose(); + ws[wasm_ws_pending_send_buffer] = null; + + // if the remaining send buffer is small, we don't block so that the throughput doesn't suffer. + // Otherwise we block so that we apply some backpresure to the application sending large data. + // this is different from Managed implementation + if (ws.bufferedAmount < ws_send_buffer_blocking_threshold) { + return null; + } - // block the promise/task until the browser passed the buffer to OS - const { promise, promise_control } = _create_cancelable_promise(); - const pending = ws[wasm_ws_pending_send_promises]; - pending.push(promise_control); + // block the promise/task until the browser passed the buffer to OS + const { promise, promise_control } = create_cancelable_promise(); + const pending = ws[wasm_ws_pending_send_promises]; + pending.push(promise_control); - let nextDelay = 1; - const polling_check = () => { - // was it all sent yet ? - if (ws.bufferedAmount === 0) { - promise_control.resolve(null); - } - else if (ws.readyState != WebSocket.OPEN) { - // only reject if the data were not sent - // bufferedAmount does not reset to zero once the connection closes - promise_control.reject("InvalidState: The WebSocket is not connected."); - } - else if (!promise_control.isDone) { - globalThis.setTimeout(polling_check, nextDelay); - // exponentially longer delays, up to 1000ms - nextDelay = Math.min(nextDelay * 1.5, 1000); - return; - } - // remove from pending - const index = pending.indexOf(promise_control); - if (index > -1) { - pending.splice(index, 1); - } - }; + let nextDelay = 1; + const polling_check = () => { + // was it all sent yet ? + if (ws.bufferedAmount === 0) { + promise_control.resolve(null); + } + else if (ws.readyState != WebSocket.OPEN) { + // only reject if the data were not sent + // bufferedAmount does not reset to zero once the connection closes + promise_control.reject("InvalidState: The WebSocket is not connected."); + } + else if (!promise_control.isDone) { + globalThis.setTimeout(polling_check, nextDelay); + // exponentially longer delays, up to 1000ms + nextDelay = Math.min(nextDelay * 1.5, 1000); + return; + } + // remove from pending + const index = pending.indexOf(promise_control); + if (index > -1) { + pending.splice(index, 1); + } + }; - globalThis.setTimeout(polling_check, 0); + globalThis.setTimeout(polling_check, 0); - const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root); - // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC - Module.setValue(thenable_js_handle, <any>then_js_handle, "i32"); - } finally { - result_root.release(); - } + return promise; } function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: MessageEvent) { @@ -361,36 +261,37 @@ function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: Message while (promise_queue.getLength() && event_queue.getLength()) { const promise_control = promise_queue.dequeue()!; _mono_wasm_web_socket_receive_buffering(event_queue, - promise_control.buffer_ptr, promise_control.buffer_offset, promise_control.buffer_length, - promise_control.response_ptr); + promise_control.bufferView, promise_control.responseView); promise_control.resolve(null); } prevent_timer_throttling(); } -function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, buffer_ptr: VoidPtr, buffer_offset: number, buffer_length: number, response_ptr: VoidPtr) { +function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, bufferView: ArraySegment, responseView: ArraySegment) { const event = event_queue.peek(); - const count = Math.min(buffer_length, event.data.length - event.offset); + const count = Math.min(bufferView.length, event.data.length - event.offset); if (count > 0) { - const targetView = Module.HEAPU8.subarray(<any>buffer_ptr + buffer_offset, <any>buffer_ptr + buffer_offset + buffer_length); const sourceView = event.data.subarray(event.offset, event.offset + count); - targetView.set(sourceView, 0); + bufferView.set(sourceView, 0); event.offset += count; } const end_of_message = event.data.length === event.offset ? 1 : 0; if (end_of_message) { event_queue.dequeue(); } - setI32(<any>response_ptr + 0, count); - setI32(<any>response_ptr + 4, event.type); - setI32(<any>response_ptr + 8, end_of_message); + + const response = new Int32Array([count, event.type, end_of_message]); + responseView.set(response); + + bufferView.dispose(); + responseView.dispose(); } -function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr: VoidPtr, buffer_offset: number, length: number, message_type: number, end_of_message: boolean): Uint8Array | string | null { +function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, bufferView: ArraySegment, message_type: number, end_of_message: boolean): Uint8Array | string | null { let buffer = ws[wasm_ws_pending_send_buffer]; let offset = 0; - const message_ptr = <any>buffer_ptr + buffer_offset; + const length = bufferView.length; if (buffer) { offset = ws[wasm_ws_pending_send_buffer_offset]; @@ -398,15 +299,14 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr message_type = ws[wasm_ws_pending_send_buffer_type]; // if not empty message, append to existing buffer if (length !== 0) { - const view = Module.HEAPU8.subarray(message_ptr, message_ptr + length); if (offset + length > buffer.length) { const newbuffer = new Uint8Array((offset + length + 50) * 1.5); // exponential growth newbuffer.set(buffer, 0);// copy previous buffer - newbuffer.set(view, offset);// append copy at the end + bufferView.copyTo(newbuffer.subarray(offset));// append copy at the end ws[wasm_ws_pending_send_buffer] = buffer = newbuffer; } else { - buffer.set(view, offset);// append copy at the end + bufferView.copyTo(buffer.subarray(offset));// append copy at the end } offset += length; ws[wasm_ws_pending_send_buffer_offset] = offset; @@ -415,8 +315,7 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr else if (!end_of_message) { // create new buffer if (length !== 0) { - const view = Module.HEAPU8.subarray(message_ptr, message_ptr + length); - buffer = new Uint8Array(view); // copy + buffer = <Uint8Array>bufferView.slice(); // copy offset = length; ws[wasm_ws_pending_send_buffer_offset] = offset; ws[wasm_ws_pending_send_buffer] = buffer; @@ -424,10 +323,9 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr ws[wasm_ws_pending_send_buffer_type] = message_type; } else { - // use the buffer only localy if (length !== 0) { - const memoryView = Module.HEAPU8.subarray(message_ptr, message_ptr + length); - buffer = memoryView; // send will make a copy + // we could use the unsafe view, because it will be immediately used in ws.send() + buffer = <Uint8Array>bufferView._unsafe_create_view(); offset = length; } } @@ -469,10 +367,8 @@ type WebSocketExtension = WebSocket & { } type ReceivePromiseControl = PromiseControl & { - response_ptr: VoidPtr - buffer_ptr: VoidPtr - buffer_offset: number - buffer_length: number + bufferView: ArraySegment, + responseView: ArraySegment } type Message = { |