From ca527ba36844ea8c6c04283f90398f5277c7b5fb Mon Sep 17 00:00:00 2001 From: Kenneth Pouncey Date: Mon, 1 Jun 2020 07:37:19 +0200 Subject: [PATCH] [wasm] Initial addition of Browser WebAssembly support (#35573) * Add Browser specific files to the project * Browser specific file modification to not include Quic support * Add Browser specific support files for Http Handler code * Add Interop code for JavaScript support * Remove unused reference * Add Http handler bindings implementation - This still needs to have the code implement nullable * Nullable support * Add browser files back after upstream merge conflict * Use attribute Link syntax for Common files to bring in sync with existing format * Address Missing license header * Code formatting and removal of comment code. * Address PR comments. * Address commit comments * Add blank line between License and first line of code. Address comments * Replace SocketsHttpHandler build for Browser. - Throws `PlatformNotSupportedException` for properties and methods of the HttpMessageHandler abstract implementation. * Cleanup SendAsync code when doing the call out to JavaScript Fetch. - Addresses commit comments. * Refactor BrowserHttpHandler code. * Cleanup * Remove null check as it should be checked in outer classes * Cleanup var usage to use explicit type. Address commit comments - For all of these vars, please replace them with the actual type, except when the type is obvious from a new or explicit cast on the right-hand side. * Move `.ConfigureAwait(true)` to `.ConfigureAwait(false)` - Address review comments * Change accessor of Runtime javascript interop methods. * Change accessor of Runtime javascript interop methods. * Address review comments for unused code * Cleanup leftover debug WriteLines * Remove the AllowNull attributes as per review comments. * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs Co-Authored-By: Marek Safar * throw different exception type as per review comment * use char[] array instead and return new string(array). - Address review comments * Initial addition of Browser Interop library * use char[] array instead and return new string(array). - Address review comments * Add project references so the Interop.JavaScript project builds * Coding Style update * Split the runtime methods into two modules. - .Api contains the methods that are only used internally from JavaScript bindings code. * Move System.Runtime.Interop.JavaScript to \src\libraries - Address review comments * Change preprocessor to upper case TARGETS_BROWSER - Address review comments * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.Api.cs Co-authored-by: Marek Safar * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.Api.cs Co-authored-by: Marek Safar * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.Api.cs Co-authored-by: Marek Safar * Rename from .Api to .Bridge to address review comments * Modify .csproj files to reflect .Bridge.cs change. * Remove unnecessary constant * Add docs * Update Bridge link * Collapse code as per review comment. * Address review comments on `Task.ConfigureAwait(continueOnCapturedContext: true)`. - Added comment in source code. * Address review comment about using PropertyInfo reflection. - GetMethod("get_Result") is less expensive to use. * Coding Style - Address review comments * Part of code style and object documentation to partially address review comments. * Update src/libraries/Common/src/Interop/Browser/Interop.JavaScript.JSObject.cs Co-authored-by: Stephen Toub * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.Bridge.cs Co-authored-by: Stephen Toub * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Add class docs and code style updates - Address review comments * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Fix badly formed XML * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Remove unnecessary Property implementations. * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Update code style involving the use of `var` * Modify the underlying sockets field name. - address review comments. * Style and address review comments * Remove reliance on ConnectHelper.cs code as it is not supported on Browser - This removes the reliance on the QUIC support. * Update src/libraries/Common/src/Interop/Browser/Interop.Runtime.Bridge.cs Co-authored-by: Stephen Toub * Remove null check for httpresponse.Content as it is always assigned to. - Address review comments * Address review comments * Remove methods that will not be implemented right now, * Add properties back to fix build error - error : MembersMustExist : Member 'public System.Boolean System.Net.Http.SocketsHttpHandler.XXXXXXXXXXXX.get()' does not exist in the implementation but it does exist in the contract. * Remove tcs TaskCompletionSource to address review comments * code style change * Remove disposing of _abortCts here as it causes runtime errors as being disposed of too early * Address review comment for GetType() * Rename library as per discussions * Add lock around the _boundObjects and _rawToJS access. - Address concurrency review comments * Address review comments for disposing of CancellationTokenSource * Address new HttpContent overloads in .NET 5 that take CancellationToken * Add project and test structure for JavaScript InteropServices * Remove previous modification as it is no longer needed. * Update src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs Co-authored-by: Stephen Toub * Remove custom message passed to the PNSE .ctor * Update `mono` to dotnet or .NET * Address review comment for internal sealed class * Address extra message text * Move these source modules to System.Runtime.InteropServices.JavaScript * Add interop source modules to System.Runtime.InteropServices.JavaScript project * Remove placeholder source * Reference System.Runtime.InteropServices.JavaScript project * Address review comments Unsafe.SizeOf * Finish move of sources to InteropServices.JavaScript * Remove ActiveIssue to address review comments * Add core implementations * Add core object implementation for Map * Remove unused targetframework * Fix PNSE in HttpClientHandler .ctor - When creating an instance of the browser webassembly `System.Net.Http.BrowserHttpHandler` the following error is thrown. ``` System.PlatformNotSupportedException: Operation is not supported on this platform. at System.Net.Http.BrowserHttpHandler.get_SslOptions() at System.Net.Http.HttpClientHandler.ThrowForModifiedManagedSslOptionsIfStarted() at System.Net.Http.HttpClientHandler.set_ClientCertificateOptions(ClientCertificateOption value) at System.Net.Http.HttpClientHandler..ctor() at System.Net.Http.HttpClient..ctor() ``` * Remove extra parameters to `PlatformNotSupportedException` * Fix tests due to underlying field name changing and reflection being used to obtain the socket field Co-authored-by: Marek Safar Co-authored-by: Stephen Toub --- .../src/Interop/Browser/Interop.Runtime.cs | 113 ++++ .../tests/System/Net/Http/TestHelper.cs | 2 +- .../src/System.Net.Http.csproj | 177 ++++-- .../BrowserHttpHandler/BrowserHttpHandler.cs | 534 ++++++++++++++++++ .../BrowserHttpHandler/SocketsHttpHandler.cs | 136 +++++ .../SystemProxyInfo.Browser.cs | 15 + .../src/System/Net/Http/HttpClientHandler.cs | 111 ++-- ...lientHandlerTestBase.SocketsHttpHandler.cs | 2 +- .../Directory.Build.props | 7 + ...tem.Runtime.InteropServices.JavaScript.sln | 61 ++ ....Runtime.InteropServices.JavaScript.csproj | 42 ++ .../InteropServices/JavaScript/AnyRef.cs | 27 + .../InteropServices/JavaScript/Array.cs | 101 ++++ .../InteropServices/JavaScript/ArrayBuffer.cs | 58 ++ .../InteropServices/JavaScript/CoreObject.cs | 35 ++ .../InteropServices/JavaScript/DataView.cs | 225 ++++++++ .../JavaScript/Float32Array.cs | 40 ++ .../JavaScript/Float64Array.cs | 40 ++ .../InteropServices/JavaScript/Function.cs | 56 ++ .../InteropServices/JavaScript/HostObject.cs | 41 ++ .../InteropServices/JavaScript/Int16Array.cs | 42 ++ .../InteropServices/JavaScript/Int32Array.cs | 40 ++ .../InteropServices/JavaScript/Int8Array.cs | 52 ++ .../InteropServices/JavaScript/JSException.cs | 16 + .../InteropServices/JavaScript/JSObject.cs | 152 +++++ .../Runtime/InteropServices/JavaScript/Map.cs | 271 +++++++++ .../InteropServices/JavaScript/Runtime.cs | 423 ++++++++++++++ .../JavaScript/SharedArrayBuffer.cs | 37 ++ .../InteropServices/JavaScript/TypedArray.cs | 225 ++++++++ .../InteropServices/JavaScript/Uint16Array.cs | 42 ++ .../InteropServices/JavaScript/Uint32Array.cs | 41 ++ .../InteropServices/JavaScript/Uint8Array.cs | 49 ++ .../JavaScript/Uint8ClampedArray.cs | 49 ++ .../tests/AssemblyInfo.cs | 5 + ...me.InteropServices.JavaScript.Tests.csproj | 11 + .../JavaScript/JavaScriptTests.cs | 12 + 36 files changed, 3189 insertions(+), 101 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SystemProxyInfo.Browser.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/AssemblyInfo.cs create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Tests.csproj create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs new file mode 100644 index 00000000000000..e3b0398841fa28 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using JSObject = System.Runtime.InteropServices.JavaScript.JSObject; +using JSException = System.Runtime.InteropServices.JavaScript.JSException; + +internal static partial class Interop +{ + internal static partial class Runtime + { + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern string InvokeJS(string str, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object CompileFunction(string str, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object InvokeJSWithArgs(int jsObjHandle, string method, object?[] parms, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object GetObjectProperty(int jsObjHandle, string propertyName, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object SetObjectProperty(int jsObjHandle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object GetByIndex(int jsObjHandle, int index, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object SetByIndex(int jsObjHandle, int index, object? value, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object GetGlobalObject(string? globalName, out int exceptionalResult); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object ReleaseHandle(int jsObjHandle, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object ReleaseObject(int jsObjHandle, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object NewObjectJS(int jsObjHandle, object[] parms, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object BindCoreObject(int jsObjHandle, int gcHandle, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object BindHostObject(int jsObjHandle, int gcHandle, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object New(string className, object[] parms, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object TypedArrayToArray(int jsObjHandle, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object TypedArrayCopyTo(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object TypedArrayFrom(int arrayPtr, int begin, int end, int bytesPerElement, int type, out int exceptionalResult); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern object TypedArrayCopyFrom(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult); + + // / + // / Execute the provided string in the JavaScript context + // / + // / The js. + // / String. + public static string InvokeJS(string str) + { + string res = InvokeJS(str, out int exception); + if (exception != 0) + throw new JSException(res); + return res; + } + + public static System.Runtime.InteropServices.JavaScript.Function? CompileFunction(string snippet) + { + object res = CompileFunction(snippet, out int exception); + if (exception != 0) + throw new JSException((string)res); + return res as System.Runtime.InteropServices.JavaScript.Function; + } + + public static int New(params object[] parms) + { + object res = New(typeof(T).Name, parms, out int exception); + if (exception != 0) + throw new JSException((string)res); + return (int)res; + } + + public static int New(string hostClassName, params object[] parms) + { + object res = New(hostClassName, parms, out int exception); + if (exception != 0) + throw new JSException((string)res); + return (int)res; + } + + public static JSObject? NewJSObject(JSObject? jsFuncPtr = null, params object[] parms) + { + object res = NewObjectJS(jsFuncPtr?.JSHandle ?? 0, parms, out int exception); + if (exception != 0) + throw new JSException((string)res); + return res as JSObject; + } + public static object GetGlobalObject(string? str = null) + { + int exception; + object globalHandle = Runtime.GetGlobalObject(str, out exception); + + if (exception != 0) + throw new JSException($"Error obtaining a handle to global {str}"); + + return globalHandle; + } + + } +} diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs index c3f63a84ee3c73..3c88a428908999 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -120,7 +120,7 @@ public static void EnableUnencryptedHttp2IfNecessary(HttpClientHandler handler) return; } - FieldInfo socketsHttpHandlerField = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo socketsHttpHandlerField = typeof(HttpClientHandler).GetField("_underlyingHandler", BindingFlags.NonPublic | BindingFlags.Instance); if (socketsHttpHandlerField == null) { // Not using .NET Core implementation, i.e. could be .NET Framework. 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 2f7eb9dc48f43e..1bd8d89fc682a2 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -1,4 +1,4 @@ - + Library System.Net.Http @@ -7,12 +7,15 @@ $(NoWarn);0436;CS1573 true $(DefineConstants);HTTP_DLL - $(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Linux;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)-FreeBSD + $(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Linux;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)-FreeBSD;$(NetCoreAppCurrent)-Browser enable $(DefineConstants);SYSNETHTTP_NO_OPENSSL + + $(DefineConstants);TARGETS_BROWSER + @@ -116,7 +119,7 @@ - + @@ -166,56 +169,6 @@ Link="Common\System\Net\NTAuthentication.Common.cs" /> - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Common\System\Net\Http\aspnetcore\IHttpHeadersHandler.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\DynamicTable.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HeaderField.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HPackDecoder.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HPackDecodingException.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HPackEncoder.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HPackEncodingException.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\Huffman.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\HuffmanDecodingException.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\IntegerDecoder.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\IntegerEncoder.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\H2StaticTable.cs + + + Common\System\Net\Http\aspnetcore\Http2\Hpack\StatusCodes.cs + + + Common\System\Net\Http\aspnetcore\Http3\Http3SettingType.cs + + + Common\System\Net\Http\aspnetcore\Http3\Http3StreamType.cs + + + Common\System\Net\Http\aspnetcore\Http3\Helpers\VariableLengthIntegerHelper.cs + + + Common\System\Net\Http\aspnetcore\Http3\Frames\Http3ErrorCode.cs + + + Common\System\Net\Http\aspnetcore\Http3\Frames\Http3Frame.cs + + + Common\System\Net\Http\aspnetcore\Http3\Frames\Http3FrameType.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\HeaderField.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\QPackDecoder.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\QPackDecodingException.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\QPackEncoder.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\QPackEncodingException.cs + + + Common\System\Net\Http\aspnetcore\Http3\QPack\H3StaticTable.cs + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 00000000000000..a046f0a1fe300e --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs @@ -0,0 +1,534 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using JSObject = System.Runtime.InteropServices.JavaScript.JSObject; +using JSException = System.Runtime.InteropServices.JavaScript.JSException; +using HostObject = System.Runtime.InteropServices.JavaScript.HostObject; +using Uint8Array = System.Runtime.InteropServices.JavaScript.Uint8Array; +using Function = System.Runtime.InteropServices.JavaScript.Function; + +namespace System.Net.Http +{ + // **Note** on `Task.ConfigureAwait(continueOnCapturedContext: true)` for the WebAssembly Browser. + // The current implementation of WebAssembly for the Browser does not have a SynchronizationContext nor a Scheduler + // thus forcing the callbacks to run on the main browser thread. When threading is eventually implemented using + // emscripten's threading model of remote worker threads, via SharedArrayBuffer, any API calls will have to be + // remoted back to the main thread. Most APIs only work on the main browser thread. + // During discussions the concensus has been that it will not matter right now which value is used for ConfigureAwait + // 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)System.Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("fetch"); + private static readonly JSObject? s_window = (JSObject)System.Runtime.InteropServices.JavaScript.Runtime.GetGlobalObject("window"); + + /// + /// Gets whether the current Browser supports streaming responses + /// + private static bool StreamingSupported { get; } + + static BrowserHttpHandler() + { + using (var streamingSupported = new Function("return typeof Response !== 'undefined' && 'body' in Response.prototype && typeof ReadableStream === 'function'")) + StreamingSupported = (bool)streamingSupported.Call(); + } + + public bool UseCookies + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public CookieContainer CookieContainer + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public DecompressionMethods AutomaticDecompression + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public bool UseProxy + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public IWebProxy? Proxy + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public ICredentials? DefaultProxyCredentials + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public bool PreAuthenticate + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public ICredentials? Credentials + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public bool AllowAutoRedirect + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public int MaxAutomaticRedirections + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public int MaxConnectionsPerServer + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public int MaxResponseHeadersLength + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public SslClientAuthenticationOptions SslOptions + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public IDictionary Properties => throw new PlatformNotSupportedException(); + + protected internal override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + var requestObject = new JSObject(); + + if (request.Properties.TryGetValue("WebAssemblyFetchOptions", out object? fetchOptionsValue) && + fetchOptionsValue is IDictionary fetchOptions) + { + foreach (KeyValuePair item in fetchOptions) + { + requestObject.SetObjectProperty(item.Key, item.Value); + } + } + + requestObject.SetObjectProperty("method", request.Method.Method); + + // We need to check for body content + if (request.Content != null) + { + if (request.Content is StringContent) + { + requestObject.SetObjectProperty("body", await request.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: true)); + } + else + { + using (Uint8Array uint8Buffer = Uint8Array.From(await request.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: true))) + { + requestObject.SetObjectProperty("body", uint8Buffer); + } + } + } + + // Process headers + // Cors has its own restrictions on headers. + // https://developer.mozilla.org/en-US/docs/Web/API/Headers + using (HostObject jsHeaders = new HostObject("Headers")) + { + foreach (KeyValuePair> header in request.Headers) + { + foreach (string value in header.Value) + { + jsHeaders.Invoke("append", header.Key, value); + } + } + if (request.Content != null) + { + foreach (KeyValuePair> header in request.Content.Headers) + { + foreach (string value in header.Value) + { + jsHeaders.Invoke("append", header.Key, value); + } + } + } + requestObject.SetObjectProperty("headers", jsHeaders); + } + + + WasmHttpReadStream? wasmHttpReadStream = null; + + JSObject abortController = new HostObject("AbortController"); + JSObject signal = (JSObject)abortController.GetObjectProperty("signal"); + requestObject.SetObjectProperty("signal", signal); + signal.Dispose(); + + CancellationTokenSource abortCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + CancellationTokenRegistration abortRegistration = abortCts.Token.Register((Action)(() => + { + if (abortController.JSHandle != -1) + { + abortController.Invoke("abort"); + abortController?.Dispose(); + } + wasmHttpReadStream?.Dispose(); + abortCts.Dispose(); + })); + + var args = new System.Runtime.InteropServices.JavaScript.Array(); + if (request.RequestUri != null) + { + args.Push(request.RequestUri.ToString()); + args.Push(requestObject); + } + + requestObject.Dispose(); + + var response = s_fetch?.Invoke("apply", s_window, args) as Task; + args.Dispose(); + if (response == null) + throw new Exception("Internal error marshalling the response Promise from `fetch`."); + + JSObject t = (JSObject)await response.ConfigureAwait(continueOnCapturedContext: true); + + var status = new WasmFetchResponse(t, abortController, abortCts, abortRegistration); + + HttpResponseMessage httpResponse = new HttpResponseMessage((HttpStatusCode)status.Status); + + bool streamingEnabled = request.Properties.TryGetValue("WebAssemblyEnableStreamingResponse", out object? streamingEnabledValue) && (bool)(streamingEnabledValue ?? false); + + httpResponse.Content = StreamingSupported && streamingEnabled + ? new StreamContent(wasmHttpReadStream = new WasmHttpReadStream(status)) + : (HttpContent)new BrowserHttpContent(status); + + // 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 = status.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 (!httpResponse.Headers.TryAddWithoutValidation(name, value)) + httpResponse.Content.Headers.TryAddWithoutValidation(name, value); + } + nextResult?.Dispose(); + nextResult = (JSObject)entriesIterator.Invoke("next"); + } + } + finally + { + nextResult?.Dispose(); + } + } + } + } + return httpResponse; + + } + catch (JSException jsExc) + { + throw new System.Net.Http.HttpRequestException(jsExc.Message); + } + } + + private class WasmFetchResponse : IDisposable + { + private readonly JSObject _fetchResponse; + private readonly JSObject _abortController; + private readonly CancellationTokenSource _abortCts; + private readonly CancellationTokenRegistration _abortRegistration; + + public WasmFetchResponse(JSObject fetchResponse, JSObject abortController, CancellationTokenSource abortCts, CancellationTokenRegistration abortRegistration) + { + _fetchResponse = fetchResponse ?? throw new ArgumentNullException(nameof(fetchResponse)); + _abortController = abortController ?? throw new ArgumentNullException(nameof(abortController)); + _abortCts = abortCts; + _abortRegistration = abortRegistration; + } + + 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 ArrayBuffer() => (Task)_fetchResponse.Invoke("arrayBuffer"); + public Task Text() => (Task)_fetchResponse.Invoke("text"); + public Task JSON() => (Task)_fetchResponse.Invoke("json"); + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Free any other managed objects here. + // + _abortCts.Cancel(); + _abortCts.Dispose(); + _abortRegistration.Dispose(); + } + + // Free any unmanaged objects here. + // + _fetchResponse?.Dispose(); + _abortController?.Dispose(); + } + + } + + private sealed class BrowserHttpContent : HttpContent + { + private byte[]? _data; + private readonly WasmFetchResponse _status; + + public BrowserHttpContent(WasmFetchResponse status) + { + _status = status ?? throw new ArgumentNullException(nameof(status)); + } + + private async Task GetResponseData() + { + if (_data != null) + { + return _data; + } + + 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(); + } + } + + return _data; + } + + protected override async Task CreateContentReadStreamAsync() + { + byte[] data = await GetResponseData().ConfigureAwait(continueOnCapturedContext: true); + return new MemoryStream(data, writable: false); + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => + SerializeToStreamAsync(stream, context, CancellationToken.None); + protected sealed override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) + { + byte[] data = await GetResponseData().ConfigureAwait(continueOnCapturedContext: true); + await stream.WriteAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: true); + } + protected internal override bool TryComputeLength(out long length) + { + if (_data != null) + { + length = _data.Length; + return true; + } + + length = 0; + return false; + } + + protected override void Dispose(bool disposing) + { + _status?.Dispose(); + base.Dispose(disposing); + } + } + + private sealed class WasmHttpReadStream : Stream + { + private WasmFetchResponse? _status; + private JSObject? _reader; + + private byte[]? _bufferedBytes; + private int _position; + + public WasmHttpReadStream(WasmFetchResponse status) + { + _status = status; + } + + 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(); + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if (count < 0 || buffer.Length - offset < count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (_reader == null) + { + // If we've read everything, then _reader and _status will be null + if (_status == null) + { + return 0; + } + + try + { + using (JSObject body = _status.Body) + { + _reader = (JSObject)body.Invoke("getReader"); + } + } + catch (JSException) + { + cancellationToken.ThrowIfCancellationRequested(); + throw; + } + } + + if (_bufferedBytes != null && _position < _bufferedBytes.Length) + { + return ReadBuffered(); + } + + try + { + var t = (Task)_reader.Invoke("read"); + using (var read = (JSObject)await t.ConfigureAwait(continueOnCapturedContext: true)) + { + if ((bool)read.GetObjectProperty("done")) + { + _reader.Dispose(); + _reader = null; + + _status?.Dispose(); + _status = null; + return 0; + } + + _position = 0; + // value for fetch streams is a Uint8Array + using (Uint8Array binValue = (Uint8Array)read.GetObjectProperty("value")) + _bufferedBytes = binValue.ToArray(); + } + } + catch (JSException) + { + cancellationToken.ThrowIfCancellationRequested(); + throw; + } + + return ReadBuffered(); + + int ReadBuffered() + { + int n = _bufferedBytes.Length - _position; + if (n > count) + n = count; + if (n <= 0) + return 0; + + Buffer.BlockCopy(_bufferedBytes, _position, buffer, offset, n); + _position += n; + + return n; + } + } + + protected override void Dispose(bool disposing) + { + _reader?.Dispose(); + _status?.Dispose(); + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Synchronous reads are not supported, use ReadAsync instead"); + } + + public override long Seek(long offset, SeekOrigin origin) + { + 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(); + } + } + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs new file mode 100644 index 00000000000000..a2d41cdc2851a5 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +namespace System.Net.Http +{ + public sealed class SocketsHttpHandler : HttpMessageHandler + { + public bool UseCookies + { + get => throw new PlatformNotSupportedException("Property UseCookies is not supported."); + set => throw new PlatformNotSupportedException("Property UseCookies is not supported."); + } + + [AllowNull] + public CookieContainer CookieContainer + { + get => throw new PlatformNotSupportedException("Property CookieContainer is not supported."); + set => throw new PlatformNotSupportedException("Property CookieContainer is not supported."); + } + + public DecompressionMethods AutomaticDecompression + { + get => throw new PlatformNotSupportedException("Property AutomaticDecompression is not supported."); + set => throw new PlatformNotSupportedException("Property AutomaticDecompression is not supported."); + } + + public bool UseProxy + { + get => throw new PlatformNotSupportedException("Property UseProxy is not supported."); + set => throw new PlatformNotSupportedException("Property UseProxy is not supported."); + } + + public IWebProxy? Proxy + { + get => throw new PlatformNotSupportedException("Property Proxy is not supported."); + set => throw new PlatformNotSupportedException("Property Proxy is not supported."); + } + + public ICredentials? DefaultProxyCredentials + { + get => throw new PlatformNotSupportedException("Property Credentials is not supported."); + set => throw new PlatformNotSupportedException("Property Credentials is not supported."); + } + + public bool PreAuthenticate + { + get => throw new PlatformNotSupportedException("Property PreAuthenticate is not supported."); + set => throw new PlatformNotSupportedException("Property PreAuthenticate is not supported."); + } + + public ICredentials? Credentials + { + get => throw new PlatformNotSupportedException("Property Credentials is not supported."); + set => throw new PlatformNotSupportedException("Property Credentials is not supported."); + } + + public bool AllowAutoRedirect + { + get => throw new PlatformNotSupportedException("Property AllowAutoRedirect is not supported."); + set => throw new PlatformNotSupportedException("Property AllowAutoRedirect is not supported."); + } + + public int MaxAutomaticRedirections + { + get => throw new PlatformNotSupportedException("Property MaxAutomaticRedirections is not supported."); + set => throw new PlatformNotSupportedException("Property MaxAutomaticRedirections is not supported."); + } + + public int MaxConnectionsPerServer + { + get => throw new PlatformNotSupportedException("Property MaxConnectionsPerServer is not supported."); + set => throw new PlatformNotSupportedException("Property MaxConnectionsPerServer is not supported."); + } + + public int MaxResponseDrainSize + { + get => throw new PlatformNotSupportedException("Property MaxResponseDrainSize is not supported."); + set => throw new PlatformNotSupportedException("Property MaxResponseDrainSize is not supported."); + } + + public TimeSpan ResponseDrainTimeout + { + get => throw new PlatformNotSupportedException("Property ResponseDrainTimeout is not supported."); + set => throw new PlatformNotSupportedException("Property ResponseDrainTimeout is not supported."); + } + + public int MaxResponseHeadersLength + { + get => throw new PlatformNotSupportedException("Property MaxResponseHeadersLength is not supported."); + set => throw new PlatformNotSupportedException("Property MaxResponseHeadersLength is not supported."); + } + + [AllowNull] + public SslClientAuthenticationOptions SslOptions + { + get => throw new PlatformNotSupportedException("Property SslOptions is not supported."); + set => throw new PlatformNotSupportedException("Property SslOptions is not supported."); + } + + public TimeSpan PooledConnectionLifetime + { + get => throw new PlatformNotSupportedException("Property PooledConnectionLifetime is not supported."); + set => throw new PlatformNotSupportedException("Property PooledConnectionLifetime is not supported."); + } + + public TimeSpan PooledConnectionIdleTimeout + { + get => throw new PlatformNotSupportedException("Property PooledConnectionLifetime is not supported."); + set => throw new PlatformNotSupportedException("Property PooledConnectionLifetime is not supported."); + } + + public TimeSpan ConnectTimeout + { + get => throw new PlatformNotSupportedException("Property ConnectTimeout is not supported."); + set => throw new PlatformNotSupportedException("Property ConnectTimeout is not supported."); + } + + public TimeSpan Expect100ContinueTimeout + { + get => throw new PlatformNotSupportedException("Property Expect100ContinueTimeout is not supported."); + set => throw new PlatformNotSupportedException("Property Expect100ContinueTimeout is not supported."); + } + + public IDictionary Properties => throw new PlatformNotSupportedException("Property Properties is not supported."); + + protected internal override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) => throw new PlatformNotSupportedException("Method SendAsync is not supported."); + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SystemProxyInfo.Browser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SystemProxyInfo.Browser.cs new file mode 100644 index 00000000000000..24fffcd463d54f --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SystemProxyInfo.Browser.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal static partial class SystemProxyInfo + { + // On Browser we do not support proxy. + public static IWebProxy ConstructSystemProxy() + { + return new HttpNoProxy(); + } + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs index 304b810d548742..a4e5086b4c103a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs @@ -13,14 +13,22 @@ namespace System.Net.Http { public partial class HttpClientHandler : HttpMessageHandler { - private readonly SocketsHttpHandler _socketsHttpHandler; +#if TARGETS_BROWSER + private readonly BrowserHttpHandler _underlyingHandler; +#else + private readonly SocketsHttpHandler _underlyingHandler; +#endif private readonly DiagnosticsHandler _diagnosticsHandler; private ClientCertificateOption _clientCertificateOptions; public HttpClientHandler() { - _socketsHttpHandler = new SocketsHttpHandler(); - _diagnosticsHandler = new DiagnosticsHandler(_socketsHttpHandler); +#if TARGETS_BROWSER + _underlyingHandler = new BrowserHttpHandler(); +#else + _underlyingHandler = new SocketsHttpHandler(); +#endif + _diagnosticsHandler = new DiagnosticsHandler(_underlyingHandler); ClientCertificateOptions = ClientCertificateOption.Manual; } @@ -29,7 +37,7 @@ protected override void Dispose(bool disposing) if (disposing && !_disposed) { _disposed = true; - _socketsHttpHandler.Dispose(); + _underlyingHandler.Dispose(); } base.Dispose(disposing); @@ -41,13 +49,13 @@ protected override void Dispose(bool disposing) public bool UseCookies { - get => _socketsHttpHandler.UseCookies; - set => _socketsHttpHandler.UseCookies = value; + get => _underlyingHandler.UseCookies; + set => _underlyingHandler.UseCookies = value; } public CookieContainer CookieContainer { - get => _socketsHttpHandler.CookieContainer; + get => _underlyingHandler.CookieContainer; set { if (value == null) @@ -55,57 +63,57 @@ public CookieContainer CookieContainer throw new ArgumentNullException(nameof(value)); } - _socketsHttpHandler.CookieContainer = value; + _underlyingHandler.CookieContainer = value; } } public DecompressionMethods AutomaticDecompression { - get => _socketsHttpHandler.AutomaticDecompression; - set => _socketsHttpHandler.AutomaticDecompression = value; + get => _underlyingHandler.AutomaticDecompression; + set => _underlyingHandler.AutomaticDecompression = value; } public bool UseProxy { - get => _socketsHttpHandler.UseProxy; - set => _socketsHttpHandler.UseProxy = value; + get => _underlyingHandler.UseProxy; + set => _underlyingHandler.UseProxy = value; } public IWebProxy? Proxy { - get => _socketsHttpHandler.Proxy; - set => _socketsHttpHandler.Proxy = value; + get => _underlyingHandler.Proxy; + set => _underlyingHandler.Proxy = value; } public ICredentials? DefaultProxyCredentials { - get => _socketsHttpHandler.DefaultProxyCredentials; - set => _socketsHttpHandler.DefaultProxyCredentials = value; + get => _underlyingHandler.DefaultProxyCredentials; + set => _underlyingHandler.DefaultProxyCredentials = value; } public bool PreAuthenticate { - get => _socketsHttpHandler.PreAuthenticate; - set => _socketsHttpHandler.PreAuthenticate = value; + get => _underlyingHandler.PreAuthenticate; + set => _underlyingHandler.PreAuthenticate = value; } public bool UseDefaultCredentials { // SocketsHttpHandler doesn't have a separate UseDefaultCredentials property. There // is just a Credentials property. So, we need to map the behavior. - get => _socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials; + get => _underlyingHandler.Credentials == CredentialCache.DefaultCredentials; set { if (value) { - _socketsHttpHandler.Credentials = CredentialCache.DefaultCredentials; + _underlyingHandler.Credentials = CredentialCache.DefaultCredentials; } else { - if (_socketsHttpHandler.Credentials == CredentialCache.DefaultCredentials) + if (_underlyingHandler.Credentials == CredentialCache.DefaultCredentials) { // Only clear out the Credentials property if it was a DefaultCredentials. - _socketsHttpHandler.Credentials = null; + _underlyingHandler.Credentials = null; } } } @@ -113,32 +121,32 @@ public bool UseDefaultCredentials public ICredentials? Credentials { - get => _socketsHttpHandler.Credentials; - set => _socketsHttpHandler.Credentials = value; + get => _underlyingHandler.Credentials; + set => _underlyingHandler.Credentials = value; } public bool AllowAutoRedirect { - get => _socketsHttpHandler.AllowAutoRedirect; - set => _socketsHttpHandler.AllowAutoRedirect = value; + get => _underlyingHandler.AllowAutoRedirect; + set => _underlyingHandler.AllowAutoRedirect = value; } public int MaxAutomaticRedirections { - get => _socketsHttpHandler.MaxAutomaticRedirections; - set => _socketsHttpHandler.MaxAutomaticRedirections = value; + get => _underlyingHandler.MaxAutomaticRedirections; + set => _underlyingHandler.MaxAutomaticRedirections = value; } public int MaxConnectionsPerServer { - get => _socketsHttpHandler.MaxConnectionsPerServer; - set => _socketsHttpHandler.MaxConnectionsPerServer = value; + get => _underlyingHandler.MaxConnectionsPerServer; + set => _underlyingHandler.MaxConnectionsPerServer = value; } public int MaxResponseHeadersLength { - get => _socketsHttpHandler.MaxResponseHeadersLength; - set => _socketsHttpHandler.MaxResponseHeadersLength = value; + get => _underlyingHandler.MaxResponseHeadersLength; + set => _underlyingHandler.MaxResponseHeadersLength = value; } public ClientCertificateOption ClientCertificateOptions @@ -149,15 +157,23 @@ public ClientCertificateOption ClientCertificateOptions switch (value) { case ClientCertificateOption.Manual: +#if TARGETS_BROWSER + _clientCertificateOptions = value; +#else ThrowForModifiedManagedSslOptionsIfStarted(); _clientCertificateOptions = value; - _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(ClientCertificates)!; + _underlyingHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(ClientCertificates)!; +#endif break; case ClientCertificateOption.Automatic: +#if TARGETS_BROWSER + _clientCertificateOptions = value; +#else ThrowForModifiedManagedSslOptionsIfStarted(); _clientCertificateOptions = value; - _socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate()!; + _underlyingHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate()!; +#endif break; default: @@ -175,51 +191,56 @@ public X509CertificateCollection ClientCertificates throw new InvalidOperationException(SR.Format(SR.net_http_invalid_enable_first, nameof(ClientCertificateOptions), nameof(ClientCertificateOption.Manual))); } - return _socketsHttpHandler.SslOptions.ClientCertificates ?? - (_socketsHttpHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); + return _underlyingHandler.SslOptions.ClientCertificates ?? + (_underlyingHandler.SslOptions.ClientCertificates = new X509CertificateCollection()); } } public Func? ServerCertificateCustomValidationCallback { - get => (_socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback?.Target as ConnectHelper.CertificateCallbackMapper)?.FromHttpClientHandler; +#if TARGETS_BROWSER + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); +#else + get => (_underlyingHandler.SslOptions.RemoteCertificateValidationCallback?.Target as ConnectHelper.CertificateCallbackMapper)?.FromHttpClientHandler; set { ThrowForModifiedManagedSslOptionsIfStarted(); - _socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback = value != null ? + _underlyingHandler.SslOptions.RemoteCertificateValidationCallback = value != null ? new ConnectHelper.CertificateCallbackMapper(value).ForSocketsHttpHandler : null; } +#endif } public bool CheckCertificateRevocationList { - get => _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; + get => _underlyingHandler.SslOptions.CertificateRevocationCheckMode == X509RevocationMode.Online; set { ThrowForModifiedManagedSslOptionsIfStarted(); - _socketsHttpHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; + _underlyingHandler.SslOptions.CertificateRevocationCheckMode = value ? X509RevocationMode.Online : X509RevocationMode.NoCheck; } } public SslProtocols SslProtocols { - get => _socketsHttpHandler.SslOptions.EnabledSslProtocols; + get => _underlyingHandler.SslOptions.EnabledSslProtocols; set { ThrowForModifiedManagedSslOptionsIfStarted(); - _socketsHttpHandler.SslOptions.EnabledSslProtocols = value; + _underlyingHandler.SslOptions.EnabledSslProtocols = value; } } - public IDictionary Properties => _socketsHttpHandler.Properties; + public IDictionary Properties => _underlyingHandler.Properties; protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return DiagnosticsHandler.IsEnabled() ? _diagnosticsHandler.SendAsync(request, cancellationToken) : - _socketsHttpHandler.SendAsync(request, cancellationToken); + _underlyingHandler.SendAsync(request, cancellationToken); } public static Func DangerousAcceptAnyServerCertificateValidator { get; } = delegate { return true; }; @@ -228,7 +249,7 @@ private void ThrowForModifiedManagedSslOptionsIfStarted() { // Hack to trigger an InvalidOperationException if a property that's stored on // SslOptions is changed, since SslOptions itself does not do any such checks. - _socketsHttpHandler.SslOptions = _socketsHttpHandler.SslOptions; + _underlyingHandler.SslOptions = _underlyingHandler.SslOptions; } } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs index ae7a249db060c1..5da96c960c51d9 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs @@ -43,7 +43,7 @@ protected static void SetUsePrenegotiatedHttp3(HttpClientHandler handler, bool u protected static object GetUnderlyingSocketsHttpHandler(HttpClientHandler handler) { - FieldInfo field = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo field = typeof(HttpClientHandler).GetField("_underlyingHandler", BindingFlags.Instance | BindingFlags.NonPublic); return field?.GetValue(handler); } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props b/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props new file mode 100644 index 00000000000000..465e1110d6b07f --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props @@ -0,0 +1,7 @@ + + + + Microsoft + true + + \ No newline at end of file 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 new file mode 100644 index 00000000000000..284e65cfede9f7 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/System.Runtime.InteropServices.JavaScript.sln @@ -0,0 +1,61 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27213.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServices.JavaScript.Tests", "tests\System.Runtime.InteropServices.JavaScript.Tests.csproj", "{7A93B8A0-E663-4CB1-B6A5-850E5EC56146}" + ProjectSection(ProjectDependencies) = postProject + {4AC5343E-6E31-4BA5-A795-0493AE7E9008} = {4AC5343E-6E31-4BA5-A795-0493AE7E9008} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.InteropServices.JavaScript", "src\System.Runtime.InteropServices.JavaScript.csproj", "{4AC5343E-6E31-4BA5-A795-0493AE7E9008}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1A2F9F4A-A032-433E-B914-ADD5992BB178}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E107E9C1-E893-4E87-987E-04EF0DCEAEFD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7A93B8A0-E663-4CB1-B6A5-850E5EC56146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A93B8A0-E663-4CB1-B6A5-850E5EC56146}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A93B8A0-E663-4CB1-B6A5-850E5EC56146}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A93B8A0-E663-4CB1-B6A5-850E5EC56146}.Release|Any CPU.Build.0 = Release|Any CPU + {B0FFC4A8-BAC3-4A7F-8FD5-5B680209371C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0FFC4A8-BAC3-4A7F-8FD5-5B680209371C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0FFC4A8-BAC3-4A7F-8FD5-5B680209371C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0FFC4A8-BAC3-4A7F-8FD5-5B680209371C}.Release|Any CPU.Build.0 = Release|Any CPU + {96AF3242-A368-4F13-B006-A722CC3B8517}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96AF3242-A368-4F13-B006-A722CC3B8517}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96AF3242-A368-4F13-B006-A722CC3B8517}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96AF3242-A368-4F13-B006-A722CC3B8517}.Release|Any CPU.Build.0 = Release|Any CPU + {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4AC5343E-6E31-4BA5-A795-0493AE7E9008}.Release|Any CPU.Build.0 = Release|Any CPU + {CEFAB3B8-3B7F-4B6C-9A5F-2DCDD1A551B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEFAB3B8-3B7F-4B6C-9A5F-2DCDD1A551B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEFAB3B8-3B7F-4B6C-9A5F-2DCDD1A551B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEFAB3B8-3B7F-4B6C-9A5F-2DCDD1A551B5}.Release|Any CPU.Build.0 = Release|Any CPU + {375E5A4A-9026-411E-9CD8-606874CFF7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {375E5A4A-9026-411E-9CD8-606874CFF7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {375E5A4A-9026-411E-9CD8-606874CFF7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {375E5A4A-9026-411E-9CD8-606874CFF7F0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7A93B8A0-E663-4CB1-B6A5-850E5EC56146} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {B0FFC4A8-BAC3-4A7F-8FD5-5B680209371C} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {96AF3242-A368-4F13-B006-A722CC3B8517} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {4AC5343E-6E31-4BA5-A795-0493AE7E9008} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD} + {CEFAB3B8-3B7F-4B6C-9A5F-2DCDD1A551B5} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {375E5A4A-9026-411E-9CD8-606874CFF7F0} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2151451C-8B2A-44DB-881E-B922A4795A30} + EndGlobalSection +EndGlobal 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 new file mode 100644 index 00000000000000..11b18a25fd7f4b --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -0,0 +1,42 @@ + + + System.Runtime.InteropServices.JavaScript + true + enable + $(NetCoreAppCurrent)-Browser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs new file mode 100644 index 00000000000000..fa7a1675205402 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices.JavaScript +{ + public class AnyRef + { + public int JSHandle { get; internal set; } + internal GCHandle Handle; + + internal AnyRef(int jsHandle) + { + JSHandle = jsHandle; + Handle = GCHandle.Alloc(this); + } + + internal AnyRef(IntPtr jsHandle) + { + JSHandle = (int)jsHandle; + Handle = GCHandle.Alloc(this); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs new file mode 100644 index 00000000000000..6be725222fd79c --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// Initializes a new instance of JavaScript Core Array class. + /// + public class Array : CoreObject + { + /// + /// Initializes a new instance of the Array class. + /// + /// Parameters. + public Array(params object[] _params) : base(Interop.Runtime.New(_params)) + { } + + /// + /// Initializes a new instance of the Array/> class. + /// + /// Js handle. + internal Array(IntPtr jsHandle) : base(jsHandle) + { } + + /// + /// Push the specified elements. + /// + /// The new length of the Array push was called on + /// Elements. + public int Push(params object[] elements) => (int)Invoke("push", elements); + + /// + /// Pop this instance. + /// + /// The element removed from the array or null if the array was empty + public object Pop() => (object)Invoke("pop"); + + /// + /// Remove the first element of the Array and return that element + /// + /// The removed element + public object Shift() => Invoke("shift"); + + /// + /// Add to the array starting at index 0 + /// + /// The length after shift. + /// Elements. + public int UnShift(params object[] elements) => (int)Invoke("unshift", elements); + + /// + /// Index of the search element. + /// + /// The index of first occurrence of searchElement in the Array or -1 if not Found + /// Search element. + /// The index to start the search from + public int IndexOf(object searchElement, int fromIndex = 0) => (int)Invoke("indexOf", searchElement, fromIndex); + + /// + /// Finds the index of the last occurrence of + /// + /// The index of the last occurrence + /// Search element. + public int LastIndexOf(object searchElement) => (int)Invoke("lastIndexOf", searchElement); + + /// + /// Finds the index of the last occurrence of between 0 and . + /// + /// The index of the last occurrence. + /// Search element. + /// End index. + public int LastIndexOf(object searchElement, int endIndex) => (int)Invoke("lastIndexOf", searchElement, endIndex); + + /// + /// Gets or sets the Array with the index specified by . + /// + /// The index. + public object this[int i] + { + get + { + object indexValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception); + + if (exception != 0) + throw new JSException((string)indexValue); + return indexValue; + } + set + { + object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception); + + if (exception != 0) + throw new JSException((string)res); + + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs new file mode 100644 index 00000000000000..04e652db81cbb5 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public class ArrayBuffer : CoreObject + { + /// + /// Initializes a new instance of the JavaScript Core ArrayBuffer class. + /// + public ArrayBuffer() : base(Interop.Runtime.New()) + { } + + /// + /// Initializes a new instance of the JavaScript Core ArrayBuffer class. + /// + /// Length. + public ArrayBuffer(int length) : base(Interop.Runtime.New(length)) + { } + + /// + /// Initializes a new instance of the JavaScript Core ArrayBuffer class. + /// + /// Js handle. + internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle) + { } + + /// + /// The length of an ArrayBuffer in bytes. + /// + /// The length of the underlying ArrayBuffer in bytes. + public int ByteLength => (int)GetObjectProperty("byteLength"); + + /// + /// Gets a value indicating whether this ArrayBuffer is view. + /// + /// true if is view; otherwise, false. + public bool IsView => (bool)GetObjectProperty("isView"); + + /// + /// Slice the specified begin. + /// + /// The slice. + /// Begin. + public ArrayBuffer Slice(int begin) => (ArrayBuffer)Invoke("slice", begin); + + /// + /// Slice the specified begin and end. + /// + /// The slice. + /// Begin. + /// End. + public ArrayBuffer Slice(int begin, int end) => (ArrayBuffer)Invoke("slice", begin, end); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs new file mode 100644 index 00000000000000..24b97d16977d5a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + + /// + /// Core objects are the standard built-in objects and functions. + /// + /// + /// These objects are part of the JavaScript environment. Not to be confused + /// with objects provided by the host application or the browser context + /// such as DOM. For more information about the distinction between the + /// DOM and core JavaScript, see JavaScript technologies overview: + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview + /// + /// Core objects are treated differently in the bridge code as they are + /// guaranteed to be there. + /// + public abstract class CoreObject : JSObject + { + protected CoreObject(int jsHandle) : base(jsHandle) + { + object result = Interop.Runtime.BindCoreObject(jsHandle, (int)(IntPtr)Handle, out int exception); + if (exception != 0) + throw new JSException($"CoreObject Error binding: {result}"); + } + + internal CoreObject(IntPtr js_handle) : base(js_handle) + { } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs new file mode 100644 index 00000000000000..0fd921a8e9f11a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// The DataView view provides a low-level interface for reading and writing multiple number types in a + /// binary ArrayBuffer, without having to care about the platform's endianness. + /// + public class DataView : CoreObject + { + /// + /// Initializes a new instance of the DataView class. + /// + /// ArrayBuffer to use as the storage backing the new DataView object. + public DataView(ArrayBuffer buffer) : base(Runtime.New(buffer)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// ArrayBuffer to use as the storage backing the new DataView object. + /// The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte. + public DataView(ArrayBuffer buffer, int byteOffset) : base(Runtime.New(buffer, byteOffset)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// ArrayBuffer to use as the storage backing the new DataView object. + /// The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte. + /// The number of elements in the byte array. If unspecified, the view's length will match the buffer's length. + public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New(buffer, byteOffset, byteLength)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// SharedArrayBuffer to use as the storage backing the new DataView object. + public DataView(SharedArrayBuffer buffer) : base(Runtime.New(buffer)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// SharedArrayBuffer to use as the storage backing the new DataView object. + /// The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte. + public DataView(SharedArrayBuffer buffer, int byteOffset) : base(Runtime.New(buffer, byteOffset)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// SharedArrayBuffer to use as the storage backing the new DataView object. + /// The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte. + /// The number of elements in the byte array. If unspecified, the view's length will match the buffer's length. + public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New(buffer, byteOffset, byteLength)) + { } + + /// + /// Initializes a new instance of the DataView class. + /// + /// Js handle. + internal DataView(IntPtr js_handle) : base(js_handle) + { } + /// + /// Gets the length (in bytes) of this view from the start of its ArrayBuffer. Fixed at construction time and thus read only. + /// + /// The length (in bytes) of this view. + public int ByteLength => (int)GetObjectProperty("byteLength"); + /// + /// Gets the offset (in bytes) of this view from the start of its ArrayBuffer. Fixed at construction time and thus read only. + /// + /// The offset (in bytes) of this view. + public int ByteOffset => (int)GetObjectProperty("byteOffset"); + /// + /// Gets the ArrayBuffer referenced by this view. Fixed at construction time and thus read only. + /// + /// The ArrayBuffer. + public ArrayBuffer Buffer => (ArrayBuffer)GetObjectProperty("buffer"); + + /// + /// Gets the signed 32-bit float (float) at the specified byte offset from the start of the DataView. + /// + /// A signed 32-bit float number. + /// Byte offset. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public float GetFloat32(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getFloat32", byteOffset, littleEndian)); + + /// + /// Gets the signed 64-bit double (double) at the specified byte offset from the start of the DataView. + /// + /// A signed 64-bit coulbe number. + /// Byte offset. + /// Indicates whether the 64-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public double GetFloat64(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getFloat64", byteOffset, littleEndian)); + /// + /// Gets the signed 16-bit integer (short) at the specified byte offset from the start of the DataView. + /// + /// A signed 16-bit ineger (short) number. + /// Byte offset. + /// Indicates whether the 16-bit integer (short) is stored in little- or big-endian format. If false, a big-endian value is read. + public short GetInt16(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getInt16", byteOffset, littleEndian)); + /// + /// Gets the signed 32-bit integer (int) at the specified byte offset from the start of the DataView. + /// + /// A signed 32-bit ineger (int) number. + /// Byte offset. + /// Indicates whether the 32-bit integer (int) is stored in little- or big-endian format. If false, a big-endian value is read. + public int GetInt32(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getInt32", byteOffset, littleEndian)); + /// + /// Gets the signed 8-bit byte (sbyte) at the specified byte offset from the start of the DataView. + /// + /// A signed 8-bit byte (sbyte) number. + /// Byte offset. + /// Indicates whether the 8-bit byte is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public sbyte GetInt8(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getInt8", byteOffset, littleEndian)); + /// + /// Gets the unsigned 16-bit integer (short) at the specified byte offset from the start of the DataView. + /// + /// A unsigned 16-bit integer (ushort) number. + /// Byte offset. + /// Indicates whether the unsigned 16-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public ushort GetUint16(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getUint16", byteOffset, littleEndian)); + /// + /// Gets the usigned 32-bit integer (uint) at the specified byte offset from the start of the DataView. + /// + /// A usigned 32-bit ineger (uint) number. + /// Byte offset. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public uint GetUint32(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getUint32", byteOffset, littleEndian)); + /// + /// Gets the unsigned 8-bit byte (byte) at the specified byte offset from the start of the DataView. + /// + /// A unsigned 8-bit byte (byte) number. + /// Byte offset. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public byte GetUint8(int byteOffset, bool littleEndian = false) => UnBoxValue(Invoke("getUint8", byteOffset, littleEndian)); + /// + /// Sets the signed 32-bit float (float) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// float value. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public void SetFloat32(int byteOffset, float value, bool littleEndian = false) => Invoke("setFloat32", byteOffset, value, littleEndian); + + /// + /// Sets the signed 64-bit double (double) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// double value. + /// Indicates whether the 64-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public void SetFloat64(int byteOffset, double value, bool littleEndian = false) => Invoke("setFloat64", byteOffset, value, littleEndian); + /// + /// Sets the signed 16-bit integer (short) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// short value. + /// Indicates whether the 16-bit integer (short) is stored in little- or big-endian format. If false, a big-endian value is read. + public void SetInt16(int byteOffset, short value, bool littleEndian = false) => Invoke("setInt16", byteOffset, value, littleEndian); + /// + /// Sets the signed 32-bit integer (int) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// int value. + /// Indicates whether the 32-bit integer (int) is stored in little- or big-endian format. If false, a big-endian value is read. + public void SetInt32(int byteOffset, int value, bool littleEndian = false) => Invoke("setInt32", byteOffset, value, littleEndian); + /// + /// Sets the signed 8-bit byte (sbyte) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// sbyte value. + /// Indicates whether the 8-bit byte is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public void SetInt8(int byteOffset, sbyte value, bool littleEndian = false) => Invoke("setInt8", byteOffset, value, littleEndian); + /// + /// Sets the unsigned 16-bit integer (short) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// ushort value. + /// Indicates whether the unsigned 16-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public void SetUint16(int byteOffset, ushort value, bool littleEndian = false) => Invoke("setUint16", byteOffset, value, littleEndian); + /// + /// Sets the usigned 32-bit integer (uint) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// uint value. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + [CLSCompliant(false)] + public void SetUint32(int byteOffset, uint value, bool littleEndian = false) => Invoke("setUint32", byteOffset, value, littleEndian); + /// + /// Sets the unsigned 8-bit byte (sbyte) at the specified byte offset from the start of the DataView. + /// + /// Byte offset. + /// byte value. + /// Indicates whether the 32-bit float is stored in little- or big-endian format. If false, a big-endian value is read. + public void SetUint8(int byteOffset, byte value, bool littleEndian = false) => Invoke("setUint8", byteOffset, value, littleEndian); + + private U UnBoxValue(object jsValue) where U : struct + { + if (jsValue == null) + { + throw new InvalidCastException($"Unable to cast null to type {typeof(U)}."); + } + + var type = jsValue.GetType(); + if (type.IsPrimitive) + { + return (U)Convert.ChangeType(jsValue, typeof(U)); + } + else + { + throw new InvalidCastException($"Unable to cast object of type {type} to type {typeof(U)}."); + } + } + + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs new file mode 100644 index 00000000000000..892e35591be50e --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Float32Array : TypedArray + { + public Float32Array() { } + + public Float32Array(int length) : base(length) { } + + + public Float32Array(ArrayBuffer buffer) : base(buffer) { } + + public Float32Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Float32Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Float32Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Float32Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Float32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Float32Array(IntPtr js_handle) : base(js_handle) { } + + /// + /// Defines an implicit conversion of Float32Array class to a float + /// + public static implicit operator Span(Float32Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of float to a Float32Array class. + /// + public static implicit operator Float32Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs new file mode 100644 index 00000000000000..f5c96e97552489 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Float64Array : TypedArray + { + public Float64Array() { } + + public Float64Array(int length) : base(length) { } + + + public Float64Array(ArrayBuffer buffer) : base(buffer) { } + + public Float64Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Float64Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Float64Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Float64Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Float64Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Float64Array(IntPtr js_handle) : base(js_handle) { } + + /// + /// Defines an implicit conversion of Float64Array class to a double + /// + public static implicit operator Span(Float64Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of double to a Float64Array class. + /// + public static implicit operator Float64Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs new file mode 100644 index 00000000000000..e5abb9e62ec52f --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// The Function constructor creates a new Function object. + /// + /// + /// Calling the constructor directly can create functions dynamically, but suffers from security and similar + /// (but far less significant) performance issues similar to eval. However, unlike eval, the Function constructor + /// allows executing code in the global scope, prompting better programming habits and allowing for more efficient + /// code minification. + /// + public class Function : CoreObject + { + public Function(params object[] args) : base(Interop.Runtime.New(args)) + { } + + internal Function(IntPtr jHandle) : base(jHandle) + { } + + /// + /// The Apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object). + /// + /// The apply. + /// This argument. + /// Arguments. + public object Apply(object? thisArg = null, object[]? argsArray = null) => Invoke("apply", thisArg, argsArray); + + /// + /// Creates a new Function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. + /// + /// The bind. + /// This argument. + /// Arguments. + public Function Bind(object? thisArg = null, object[]? argsArray = null) => (Function)Invoke("bind", thisArg, argsArray); + + /// + /// Calls a function with a given `this` value and arguments provided individually. + /// + /// The result of calling the function with the specified `this` value and arguments. + /// Optional (null value). The value of this provided for the call to a function. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode, null and undefined will be replaced with the global object and primitive values will be converted to objects. + /// Optional. Arguments for the function. + public object Call(object? thisArg = null, params object[] argsArray) + { + object?[] argsList = new object[argsArray.Length + 1]; + argsList[0] = thisArg; + System.Array.Copy(argsArray, 0, argsList, 1, argsArray.Length); + return Invoke("call", argsList); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs new file mode 100644 index 00000000000000..cd5e41d960ac95 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// Host objects are object supplied by the host environment. + /// + /// + /// These objects are not part of the JavaScript environment and provided by the host application + /// or the browser context such as DOM. For more information about the distinction between the + /// DOM and core JavaScript, see JavaScript technologies overview: + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview + /// + /// Host objects are treated differently in the bridge code as they are not guaranteed to exist. + /// + public interface IHostObject + { } + + public class HostObject : HostObjectBase + { + public HostObject(string hostName, params object[] _params) : base(Interop.Runtime.New(hostName, _params)) + { } + } + + public abstract class HostObjectBase : JSObject, IHostObject + { + protected HostObjectBase(int jHandle) : base(jHandle) + { + object result = Interop.Runtime.BindHostObject(jHandle, (int)(IntPtr)Handle, out int exception); + if (exception != 0) + throw new JSException($"HostObject Error binding: {result}"); + } + + internal HostObjectBase(IntPtr js_handle) : base(js_handle) + { } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs new file mode 100644 index 00000000000000..12f8936aef2eeb --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Int16Array : TypedArray + { + public Int16Array() { } + + public Int16Array(int length) : base(length) { } + + + public Int16Array(ArrayBuffer buffer) : base(buffer) { } + + public Int16Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Int16Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Int16Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Int16Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Int16Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Int16Array(IntPtr js_handle) : base(js_handle) + { } + + /// + /// Defines an implicit conversion of Int16Array class to a short + /// + [CLSCompliant(false)] + public static implicit operator Span(Int16Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of short to a Int16Array class. + /// + public static implicit operator Int16Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs new file mode 100644 index 00000000000000..f55d426787fafa --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Int32Array : TypedArray + { + public Int32Array() { } + + public Int32Array(int length) : base(length) { } + + + public Int32Array(ArrayBuffer buffer) : base(buffer) { } + + public Int32Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Int32Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Int32Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Int32Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Int32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Int32Array(IntPtr js_handle) : base(js_handle) { } + + /// + /// Defines an implicit conversion of Int32Array class to a int + /// + public static implicit operator Span(Int32Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of int to a Int32Array class. + /// + public static implicit operator Int32Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs new file mode 100644 index 00000000000000..106baec6a79230 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + [CLSCompliant(false)] + public sealed class Int8Array : TypedArray + { + public Int8Array() + { } + + public Int8Array(int length) : base(length) + { } + + + public Int8Array(ArrayBuffer buffer) : base(buffer) + { } + + public Int8Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Int8Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + public Int8Array(SharedArrayBuffer buffer) : base(buffer) + { } + + public Int8Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Int8Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + internal Int8Array(IntPtr js_handle) : base(js_handle) + { } + + /// + /// Defines an implicit conversion of Int8Array class to a sbyte + /// + [CLSCompliant(false)] + public static implicit operator Span(Int8Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of sbyte to a Int8Array class. + /// + [CLSCompliant(false)] + public static implicit operator Int8Array(Span span) => From(span); + } +} 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 new file mode 100644 index 00000000000000..dc45a674a443cc --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSException.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// Represents an Exception initiated from the JavaScript interop code. + /// + public class JSException : Exception + { + public JSException(string msg) : base(msg) { } + } +} 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 new file mode 100644 index 00000000000000..ea099d4f65566d --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public interface IJSObject + { + int JSHandle { get; } + int Length { get; } + } + /// + /// JSObjects are wrappers for a native JavaScript object, and + /// they retain a reference to the JavaScript object for the lifetime of this C# object. + /// + public class JSObject : AnyRef, IJSObject, IDisposable + { + internal object? RawObject; + + // to detect redundant calls + public bool IsDisposed { get; internal set; } + + public JSObject() : this(Interop.Runtime.New()) + { + object result = Interop.Runtime.BindCoreObject(JSHandle, (int)(IntPtr)Handle, out int exception); + if (exception != 0) + throw new JSException($"JSObject Error binding: {result}"); + + } + + internal JSObject(IntPtr js_handle) : base(js_handle) + { } + + internal JSObject(int js_handle) : base((IntPtr)js_handle) + { } + + internal JSObject(int js_handle, object raw_obj) : base(js_handle) + { + RawObject = raw_obj; + } + + public object Invoke(string method, params object?[] args) + { + object res = Interop.Runtime.InvokeJSWithArgs(JSHandle, method, args, out int exception); + if (exception != 0) + throw new JSException((string)res); + return res; + } + + public object GetObjectProperty(string name) + { + + object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception); + + if (exception != 0) + throw new JSException((string)propertyValue); + + return propertyValue; + + } + + public void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false) + { + + object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception); + if (exception != 0) + throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{(IntPtr)Handle} raw '{RawObject != null})"); + + } + + /// + /// Gets or sets the length. + /// + /// The length. + public int Length + { + get => Convert.ToInt32(GetObjectProperty("length")); + set => SetObjectProperty("length", value, false); + } + + /// + /// Returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it). + /// + /// true, if the object has the specified property as own property, false otherwise. + /// The String name or Symbol of the property to test. + public bool HasOwnProperty(string prop) => (bool)Invoke("hasOwnProperty", prop); + + /// + /// Returns a boolean indicating whether the specified property is enumerable. + /// + /// true, if the specified property is enumerable, false otherwise. + /// The String name or Symbol of the property to test. + public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop); + + protected void FreeHandle() + { + Interop.Runtime.ReleaseHandle(JSHandle, out int exception); + if (exception != 0) + throw new JSException($"Error releasing handle on (js-obj js '{JSHandle}' .NET '{(IntPtr)Handle} raw '{RawObject != null})"); + } + + public override bool Equals(object? obj) => obj is JSObject other && JSHandle == other.JSHandle; + + public override int GetHashCode() + { + return JSHandle; + } + + ~JSObject() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + + if (!IsDisposed) + { + if (disposing) + { + + // Free any other managed objects here. + // + RawObject = null; + } + + IsDisposed = true; + + // Free any unmanaged objects here. + FreeHandle(); + + } + } + + public override string ToString() + { + return $"(js-obj js '{JSHandle}' .NET '{(IntPtr)Handle} raw '{RawObject != null})"; + } + + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs new file mode 100644 index 00000000000000..95a61d2587cfa2 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; + +namespace System.Runtime.InteropServices.JavaScript +{ + /// + /// The Map object holds key-value pairs and remembers the original insertion order of the keys. + /// Any value (both objects and primitive values) may be used as either a key or a value. + /// + public class Map : CoreObject, IDictionary + { + + /// + /// Initializes a new instance of the Map class. + /// + /// Parameters. + public Map(params object[] _params) : base(Runtime.New(_params)) + { } + + /// + /// Initializes a new instance of the Map class. + /// + /// Js handle. + internal Map(IntPtr jsHandle) : base(jsHandle) + { } + + /// + /// Gets a value indicating whether the Map object has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Gets a value indicating whether the Map object is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets an System.Collections.ICollection object containing the keys of the Map object. + /// + public ICollection Keys => new MapItemCollection(this, "keys"); + + /// + /// Gets an System.Collections.ICollection object containing the values of the Map object. + /// + public ICollection Values => new MapItemCollection(this, "values"); + + public int Count => (int)GetObjectProperty("size"); + + public bool IsSynchronized => false; + + public object SyncRoot => false; + + public void Add(object key, object? value) => Invoke("set", key, value); + + public void Clear() => Invoke("clear"); + + public bool Contains(object key) => (bool)Invoke("has", key); + + public IDictionaryEnumerator GetEnumerator() => new MapEnumerator(this); + + public void Remove(object key) => Invoke("delete", key); + + public void CopyTo(System.Array array, int index) => throw new NotImplementedException(); + + // Construct and return an enumerator. + IEnumerator IEnumerable.GetEnumerator() => new MapEnumerator(this); + + /// + /// Gets or sets the Map with the key specified by . + /// + /// The key. + public object? this[object key] + { + get + { + return Invoke("get", key); + } + set + { + Invoke("set", key, value); + } + } + + private sealed class MapEnumerator : IDictionaryEnumerator, IDisposable + { + private JSObject? _mapIterator; + private readonly Map _map; + public MapEnumerator(Map map) + { + _map = map; + } + + // Return the current item. + public object Current => new DictionaryEntry(Key, Value); + + // Return the current dictionary entry. + public DictionaryEntry Entry => (DictionaryEntry)Current; + + // Return the key of the current item. + public object Key { get; private set; } = new object(); + + // Return the value of the current item. + public object? Value { get; private set; } + + // Advance to the next item. + public bool MoveNext() + { + if (_mapIterator == null) + _mapIterator = (JSObject)_map.Invoke("entries"); + + using (var result = (JSObject)_mapIterator.Invoke("next")) + { + using (var resultValue = (Array)result.GetObjectProperty("value")) + { + if (resultValue != null) + { + Key = resultValue[0]; + Value = resultValue[1]; + } + else + { + Value = null; + } + } + return !(bool)result.GetObjectProperty("done"); + } + } + + // Reset the index to restart the enumeration. + public void Reset() + { + _mapIterator?.Dispose(); + _mapIterator = null; + } + + #region IDisposable Support + private bool _disposedValue = false; // To detect redundant calls + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + _mapIterator?.Dispose(); + _mapIterator = null; + _disposedValue = true; + } + } + + ~MapEnumerator() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } + + /// + /// Class that implements an ICollection over the "keys" or "values" + /// + private sealed class MapItemCollection : ICollection + { + private readonly Map _map; + private readonly string _iterator; // "keys" or "values" + + public MapItemCollection(Map map, string iterator) + { + _map = map; + _iterator = iterator; + + } + public int Count => _map.Count; + + public bool IsSynchronized => false; + + public object SyncRoot => this; + + public void CopyTo(System.Array array, int index) + { + throw new NotImplementedException(); + } + + // Construct and return an enumerator. + public IEnumerator GetEnumerator() => new MapItemEnumerator(this); + + /// + /// The custom enumerator used by MapItemCollection + /// + private sealed class MapItemEnumerator : IEnumerator + { + + private readonly MapItemCollection _mapItemCollection; + private JSObject? _mapItemIterator; + + public object? Current { get; private set; } + + public MapItemEnumerator(MapItemCollection mapCollection) + { + _mapItemCollection = mapCollection; + } + + public bool MoveNext() + { + if (_mapItemIterator == null) + _mapItemIterator = (JSObject)_mapItemCollection._map.Invoke(_mapItemCollection._iterator); + + var done = false; + using (var result = (JSObject)_mapItemIterator.Invoke("next")) + { + done = (bool)result.GetObjectProperty("done"); + if (!done) + Current = result.GetObjectProperty("value"); + return !done; + } + } + + public void Reset() + { + _mapItemIterator?.Dispose(); + _mapItemIterator = null; + } + + #region IDisposable Support + private bool _disposedValue = false; // To detect redundant calls + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + _mapItemIterator?.Dispose(); + _mapItemIterator = null; + _disposedValue = true; + } + } + + //TODO: override a finalizer only if Dispose (bool disposing) above has code to free unmanaged resources. + ~MapItemEnumerator() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + GC.SuppressFinalize(this); + } + #endregion + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs new file mode 100644 index 00000000000000..88e1592aafc5a6 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace System.Runtime.InteropServices.JavaScript +{ + public static class Runtime + { + // / + // / Execute the provided string in the JavaScript context + // / + // / The js. + // / String. + public static string InvokeJS(string str) + { + return Interop.Runtime.InvokeJS(str); + } + + public static System.Runtime.InteropServices.JavaScript.Function? CompileFunction(string snippet) + { + return Interop.Runtime.CompileFunction(snippet); + } + + public static int New(params object[] parms) + { + return Interop.Runtime.New(typeof(T).Name, parms); + } + + public static int New(string hostClassName, params object[] parms) + { + return Interop.Runtime.New(hostClassName, parms); + } + + public static void FreeObject(object obj) + { + lock (_rawToJS) + { + if (_rawToJS.TryGetValue(obj, out JSObject? jsobj)) + { + //raw_to_js [obj].RawObject = null; + _rawToJS.Remove(obj); + if (jsobj != null) + { + int exception; + Interop.Runtime.ReleaseObject(jsobj.JSHandle, out exception); + if (exception != 0) + throw new JSException($"Error releasing object on (raw-obj)"); + + jsobj.JSHandle = -1; + jsobj.RawObject = null; + jsobj.IsDisposed = true; + jsobj.Handle.Free(); + } + } + else + { + throw new JSException($"Error releasing object on (obj)"); + } + } + } + public static object GetGlobalObject(string? str = null) + { + return Interop.Runtime.GetGlobalObject(str); + } + + private static readonly Dictionary _boundObjects = new Dictionary(); + private static readonly Dictionary _rawToJS = new Dictionary(); + + public static int BindJSObject(int jsId, Type mappedType) + { + lock (_boundObjects) + { + if (!_boundObjects.TryGetValue(jsId, out JSObject? obj)) + { + if (mappedType != null) + { + return BindJSType(jsId, mappedType); + } + else + { + _boundObjects[jsId] = obj = new JSObject((IntPtr)jsId); + } + } + return obj == null ? 0 : (int)(IntPtr)obj.Handle; + } + } + + public static int BindCoreCLRObject(int jsId, int gcHandle) + { + GCHandle h = (GCHandle)(IntPtr)gcHandle; + JSObject? obj; + + lock (_boundObjects) + { + if (_boundObjects.TryGetValue(jsId, out JSObject? existingObj)) + { + if (existingObj?.Handle != h && h.IsAllocated) + throw new JSException($"Multiple handles pointing at js_id: {jsId}"); + + obj = existingObj; + } + else + { + obj = h.Target as JSObject; + } + return obj == null ? 0 : (int)(IntPtr)obj.Handle; + } + } + + public static int BindJSType(int jsId, Type mappedType) + { + lock (_boundObjects) + { + if (!_boundObjects.TryGetValue(jsId, out JSObject? obj)) + { + ConstructorInfo? jsobjectnew = mappedType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.ExactBinding, + null, new Type[] { typeof(IntPtr) }, null); + _boundObjects[jsId] = obj = jsobjectnew == null ? null : (JSObject)jsobjectnew.Invoke(new object[] { (IntPtr)jsId }); + } + return obj == null ? 0 : (int)(IntPtr)obj.Handle; + } + } + + public static int UnBindJSObject(int jsId) + { + lock (_boundObjects) + { + if (_boundObjects.TryGetValue(jsId, out JSObject? obj)) + { + _boundObjects.Remove(jsId); + return obj == null ? 0 : (int)(IntPtr)obj.Handle; + } + return 0; + } + } + + public static void UnBindJSObjectAndFree(int jsId) + { + lock (_boundObjects) + { + if (_boundObjects.TryGetValue(jsId, out JSObject? obj)) + { + if (_boundObjects[jsId] != null) + { + _boundObjects.Remove(jsId); + } + if (obj != null) + { + obj.JSHandle = -1; + obj.IsDisposed = true; + obj.RawObject = null; + obj.Handle.Free(); + } + } + } + } + + public static void UnBindRawJSObjectAndFree(int gcHandle) + { + GCHandle h = (GCHandle)(IntPtr)gcHandle; + JSObject? obj = h.Target as JSObject; + lock (_rawToJS) + { + if (obj?.RawObject != null) + { + _rawToJS.Remove(obj.RawObject); + + int exception; + Interop.Runtime.ReleaseHandle(obj.JSHandle, out exception); + if (exception != 0) + throw new JSException($"Error releasing handle on (js-obj js '{obj.JSHandle}' .NET '{(IntPtr)obj.Handle} raw '{obj.RawObject != null})"); + + // Calling Release Handle above only removes the reference from the JavaScript side but does not + // release the bridged JSObject associated with the raw object so we have to do that ourselves. + obj.JSHandle = -1; + obj.IsDisposed = true; + obj.RawObject = null; + + obj.Handle.Free(); + } + } + } + + public static object CreateTaskSource(int jsId) + { + return new TaskCompletionSource(); + } + + public static void SetTaskSourceResult(TaskCompletionSource tcs, object result) + { + tcs.SetResult(result); + } + + public static void SetTaskSourceFailure(TaskCompletionSource tcs, string reason) + { + tcs.SetException(new JSException(reason)); + } + + public static int GetTaskAndBind(TaskCompletionSource tcs, int jsId) + { + return BindExistingObject(tcs.Task, jsId); + } + + public static int BindExistingObject(object rawObj, int jsId) + { + JSObject? obj = rawObj as JSObject; + lock (_rawToJS) + { + if (obj == null && !_rawToJS.TryGetValue(rawObj, out obj)) + _rawToJS[rawObj] = obj = new JSObject(jsId, rawObj); + + return obj == null ? 0 : (int)(IntPtr)obj.Handle; + } + } + + public static int GetJSObjectId(object rawObj) + { + JSObject? obj = rawObj as JSObject; + lock (_rawToJS) + { + if (obj == null && !_rawToJS.TryGetValue(rawObj, out obj)) + return -1; + + return obj?.JSHandle ?? -1; + } + } + + public static object? GetDotNetObject(int gcHandle) + { + GCHandle h = (GCHandle)(IntPtr)gcHandle; + JSObject? o = h.Target as JSObject; + return o?.RawObject ?? null; + } + + public static object BoxInt(int i) + { + return i; + } + + public static object BoxDouble(double d) + { + return d; + } + + public static object BoxBool(int b) + { + return b == 0 ? false : true; + } + + public static bool IsSimpleArray(object a) + { + return a is System.Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0; + } + + public static object? GetCoreType(string coreObj) + { + Assembly asm = typeof(Runtime).Assembly; + Type? type = asm.GetType(coreObj); + return type; + + } + + [StructLayout(LayoutKind.Explicit)] + public struct IntPtrAndHandle + { + [FieldOffset(0)] + internal IntPtr ptr; + + [FieldOffset(0)] + internal RuntimeMethodHandle handle; + } + + public static string GetCallSignature(IntPtr methodHandle) + { + IntPtrAndHandle tmp = default(IntPtrAndHandle); + tmp.ptr = methodHandle; + + MethodBase? mb = MethodBase.GetMethodFromHandle(tmp.handle); + if (mb == null) + return string.Empty; + + ParameterInfo[] parms = mb.GetParameters(); + int parmsLength = parms.Length; + if (parmsLength == 0) + return string.Empty; + + char[] res = new char[parmsLength]; + + for (int c = 0; c < parmsLength; c++) + { + Type t = parms[c].ParameterType; + switch (Type.GetTypeCode(t)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Boolean: + // Enums types have the same code as their underlying numeric types + if (t.IsEnum) + res[c] = 'j'; + else + res[c] = 'i'; + break; + case TypeCode.Int64: + case TypeCode.UInt64: + // Enums types have the same code as their underlying numeric types + if (t.IsEnum) + res[c] = 'k'; + else + res[c] = 'l'; + break; + case TypeCode.Single: + res[c] = 'f'; + break; + case TypeCode.Double: + res[c] = 'd'; + break; + case TypeCode.String: + res[c] = 's'; + break; + default: + if (t == typeof(IntPtr)) + { + res[c] = 'i'; + } + else if (t == typeof(Uri)) + { + res[c] = 'u'; + } + else + { + if (t.IsValueType) + throw new NotSupportedException("ValueType arguments are not supported."); + res[c] = 'o'; + } + break; + } + } + return new string(res); + } + + public static void SetupJSContinuation(Task task, JSObject continuationObj) + { + if (task.IsCompleted) + Complete(); + else + task.GetAwaiter().OnCompleted(Complete); + + void Complete() + { + try + { + if (task.Exception == null) + { + object? result; + Type task_type = task.GetType(); + if (task_type == typeof(Task)) + { + result = System.Array.Empty(); + } + else + { + result = task_type.GetMethod("get_Result")?.Invoke(task, System.Array.Empty()); + } + continuationObj.Invoke("resolve", result); + } + else + { + continuationObj.Invoke("reject", task.Exception.ToString()); + } + } + catch (Exception e) + { + continuationObj.Invoke("reject", e.ToString()); + } + finally + { + continuationObj.Dispose(); + FreeObject(task); + } + } + } + + public static string ObjectToString(object o) + { + return o.ToString() ?? string.Empty; + } + + public static double GetDateValue(object dtv) + { + if (dtv == null) + throw new ArgumentNullException(nameof(dtv)); + if (!(dtv is DateTime dt)) + throw new InvalidCastException($"Unable to cast object of type {dtv.GetType()} to type DateTime."); + if (dt.Kind == DateTimeKind.Local) + dt = dt.ToUniversalTime(); + else if (dt.Kind == DateTimeKind.Unspecified) + dt = new DateTime(dt.Ticks, DateTimeKind.Utc); + return new DateTimeOffset(dt).ToUnixTimeMilliseconds(); + } + + public static DateTime CreateDateTime(double ticks) + { + DateTimeOffset unixTime = DateTimeOffset.FromUnixTimeMilliseconds((long)ticks); + return unixTime.DateTime; + } + + public static Uri CreateUri(string uri) + { + return new Uri(uri); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs new file mode 100644 index 00000000000000..cc3e90ac5662e7 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public class SharedArrayBuffer : CoreObject + { + /// + /// Initializes a new instance of the JavaScript Core SharedArrayBuffer class. + /// + /// The size, in bytes, of the array buffer to create. + public SharedArrayBuffer(int length) : base(Interop.Runtime.New(length)) + { } + + internal SharedArrayBuffer(IntPtr js_handle) : base(js_handle) + { } + + /// + /// The size, in bytes, of the array. This is established when the array is constructed and cannot be changed. + /// + /// The size, in bytes, of the array. + public int ByteLength => (int)GetObjectProperty("byteLength"); + + /// + /// Returns a new JavaScript Core SharedArrayBuffer whose contents are a copy of this SharedArrayBuffer's bytes from begin, + /// inclusive, up to end, exclusive. If either begin or end is negative, it refers to an index from the end + /// of the array, as opposed to from the beginning. + /// + /// a new JavaScript Core SharedArrayBuffer + /// Beginning index of copy. + /// Ending index, exclusive. + public SharedArrayBuffer Slice(int begin, int end) => (SharedArrayBuffer)Invoke("slice", begin, end); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs new file mode 100644 index 00000000000000..be2fa43cb1c6c5 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices.JavaScript +{ + public interface ITypedArray + { + int BytesPerElement { get; } + string Name { get; } + int ByteLength { get; } + ArrayBuffer Buffer { get; } + + void Set(Array array); + void Set(Array array, int offset); + + void Set(ITypedArray typedArray); + void Set(ITypedArray typedArray, int offset); + TypedArrayTypeCode GetTypedArrayType(); + } + + public interface ITypedArray where U : struct + { + T Slice(); + T Slice(int begin); + T Slice(int begin, int end); + + T SubArray(); + T SubArray(int begin); + T SubArray(int begin, int end); + } + + public enum TypedArrayTypeCode + { + Int8Array = 5, + Uint8Array = 6, + Int16Array = 7, + Uint16Array = 8, + Int32Array = 9, + Uint32Array = 10, + Float32Array = 13, + Float64Array = 14, + Uint8ClampedArray = 0xF, + } + + /// + /// Represents a JavaScript TypedArray. + /// + public abstract class TypedArray : CoreObject, ITypedArray, ITypedArray where U : struct + { + protected TypedArray() : base(Interop.Runtime.New()) + { } + + protected TypedArray(int length) : base(Interop.Runtime.New(length)) + { } + + protected TypedArray(ArrayBuffer buffer) : base(Interop.Runtime.New(buffer)) + { } + + protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New(buffer, byteOffset)) + { } + + protected TypedArray(ArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New(buffer, byteOffset, length)) + { } + + protected TypedArray(SharedArrayBuffer buffer) : base(Interop.Runtime.New(buffer)) + { } + + protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New(buffer, byteOffset)) + { } + + protected TypedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New(buffer, byteOffset, length)) + { } + + internal TypedArray(IntPtr jsHandle) : base(jsHandle) + { } + + public TypedArrayTypeCode GetTypedArrayType() + { + switch (this) + { + case Int8Array _: + return TypedArrayTypeCode.Int8Array; + case Uint8Array _: + return TypedArrayTypeCode.Uint8Array; + case Uint8ClampedArray _: + return TypedArrayTypeCode.Uint8ClampedArray; + case Int16Array _: + return TypedArrayTypeCode.Int16Array; + case Uint16Array _: + return TypedArrayTypeCode.Uint16Array; + case Int32Array _: + return TypedArrayTypeCode.Int32Array; + case Uint32Array _: + return TypedArrayTypeCode.Uint32Array; + case Float32Array _: + return TypedArrayTypeCode.Float32Array; + case Float64Array _: + return TypedArrayTypeCode.Float64Array; + default: + throw new ArrayTypeMismatchException("TypedArray is not of correct type."); + } + } + + public int BytesPerElement => (int)GetObjectProperty("BYTES_PER_ELEMENT"); + public string Name => (string)GetObjectProperty("name"); + public int ByteLength => (int)GetObjectProperty("byteLength"); + public ArrayBuffer Buffer => (ArrayBuffer)GetObjectProperty("buffer"); + + public void Fill(U value) => Invoke("fill", value); + public void Fill(U value, int start) => Invoke("fill", value, start); + public void Fill(U value, int start, int end) => Invoke("fill", value, start, end); + + public void Set(Array array) => Invoke("set", array); + public void Set(Array array, int offset) => Invoke("set", array, offset); + public void Set(ITypedArray typedArray) => Invoke("set", typedArray); + public void Set(ITypedArray typedArray, int offset) => Invoke("set", typedArray, offset); + + public T Slice() => (T)Invoke("slice"); + public T Slice(int begin) => (T)Invoke("slice", begin); + public T Slice(int begin, int end) => (T)Invoke("slice", begin, end); + + public T SubArray() => (T)Invoke("subarray"); + public T SubArray(int begin) => (T)Invoke("subarray", begin); + public T SubArray(int begin, int end) => (T)Invoke("subarray", begin, end); + + public U? this[int i] + { + get + { + object jsValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception); + + if (exception != 0) + throw new JSException((string)jsValue); + + // The value returned from the index. + return UnBoxValue(jsValue); + } + set + { + object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception); + + if (exception != 0) + throw new JSException((string)res); + + } + } + + private U? UnBoxValue(object jsValue) + { + if (jsValue == null) + return null; + + Type type = jsValue.GetType(); + return (U)Convert.ChangeType(jsValue, typeof(U)); + } + + public U[] ToArray() + { + object res = Interop.Runtime.TypedArrayToArray(JSHandle, out int exception); + + if (exception != 0) + throw new JSException((string)res); + return (U[])res; + } + + public static unsafe T From(ReadOnlySpan span) + { + // source has to be instantiated. + if (span == null) + { + throw new System.ArgumentException($"Invalid argument: {nameof(span)} can not be null."); + } + + TypedArrayTypeCode type = (TypedArrayTypeCode)Type.GetTypeCode(typeof(U)); + // Special case for Uint8ClampedArray, a clamped array which represents an array of 8-bit unsigned integers clamped to 0-255; + if (type == TypedArrayTypeCode.Uint8Array && typeof(T) == typeof(Uint8ClampedArray)) + type = TypedArrayTypeCode.Uint8ClampedArray; // This is only passed to the JavaScript side so it knows it will be a Uint8ClampedArray + + ReadOnlySpan bytes = MemoryMarshal.AsBytes(span); + fixed (byte* ptr = bytes) + { + object res = Interop.Runtime.TypedArrayFrom((int)ptr, 0, span.Length, Unsafe.SizeOf(), (int)type, out int exception); + if (exception != 0) + throw new JSException((string)res); + return (T)res; + } + + } + + public unsafe int CopyTo(Span span) + { + ReadOnlySpan bytes = MemoryMarshal.AsBytes(span); + fixed (byte* ptr = bytes) + { + object res = Interop.Runtime.TypedArrayCopyTo(JSHandle, (int)ptr, 0, span.Length, Unsafe.SizeOf(), out int exception); + if (exception != 0) + throw new JSException((string)res); + return (int)res / Unsafe.SizeOf(); + } + } + + public unsafe int CopyFrom(ReadOnlySpan span) + { + // source has to be instantiated. + if (span == null || span.Length == 0) + { + throw new System.ArgumentException($"Invalid argument: {nameof(span)} can not be null and must have a length"); + } + + ReadOnlySpan bytes = MemoryMarshal.AsBytes(span); + fixed (byte* ptr = bytes) + { + object res = Interop.Runtime.TypedArrayCopyFrom(JSHandle, (int)ptr, 0, span.Length, Unsafe.SizeOf(), out int exception); + if (exception != 0) + throw new JSException((string)res); + return (int)res / Unsafe.SizeOf(); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs new file mode 100644 index 00000000000000..8c3a42dee25c07 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + [CLSCompliant(false)] + public sealed class Uint16Array : TypedArray + { + public Uint16Array() { } + + public Uint16Array(int length) : base(length) { } + + + public Uint16Array(ArrayBuffer buffer) : base(buffer) { } + + public Uint16Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Uint16Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Uint16Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Uint16Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Uint16Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Uint16Array(IntPtr js_handle) : base(js_handle) + { } + + /// + /// Defines an implicit conversion of Uint16Array class to a ushort + /// + public static implicit operator Span(Uint16Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of ushort to a Uint16Array class. + /// + public static implicit operator Uint16Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs new file mode 100644 index 00000000000000..5e4d14e5944655 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + [CLSCompliant(false)] + public sealed class Uint32Array : TypedArray + { + public Uint32Array() { } + + public Uint32Array(int length) : base(length) { } + + + public Uint32Array(ArrayBuffer buffer) : base(buffer) { } + + public Uint32Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Uint32Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + public Uint32Array(SharedArrayBuffer buffer) : base(buffer) { } + + public Uint32Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) { } + + public Uint32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { } + + internal Uint32Array(IntPtr js_handle) : base(js_handle) { } + + /// + /// Defines an implicit conversion of Uint32Array class to a uint + /// + public static implicit operator Span(Uint32Array typedarray) => typedarray.ToArray(); + + /// + /// Defines an implicit conversion of uint to a Uint32Array class. + /// + public static implicit operator Uint32Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs new file mode 100644 index 00000000000000..cf3781df62924b --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Uint8Array : TypedArray + { + /// + /// Initializes a new instance of the JavaScript Core Uint8Array class. + /// + public Uint8Array() + { } + + public Uint8Array(int length) : base(length) + { } + + + public Uint8Array(ArrayBuffer buffer) : base(buffer) + { } + + public Uint8Array(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Uint8Array(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + public Uint8Array(SharedArrayBuffer buffer) : base(buffer) + { } + + public Uint8Array(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Uint8Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + internal Uint8Array(IntPtr js_handle) : base(js_handle) + { } + + /// + /// Defines an implicit conversion of JavaScript Core Uint8Array class to a Span<byte> + /// + public static implicit operator Span(Uint8Array typedarray) => typedarray.ToArray(); + + public static implicit operator Uint8Array(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs new file mode 100644 index 00000000000000..913d7a89a65f55 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Runtime.InteropServices.JavaScript +{ + public sealed class Uint8ClampedArray : TypedArray + { + /// + /// Initializes a new instance of the JavaScript Core Uint8ClampedArray class. + /// + public Uint8ClampedArray() + { } + + public Uint8ClampedArray(int length) : base(length) + { } + + + public Uint8ClampedArray(ArrayBuffer buffer) : base(buffer) + { } + + public Uint8ClampedArray(ArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Uint8ClampedArray(ArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + public Uint8ClampedArray(SharedArrayBuffer buffer) : base(buffer) + { } + + public Uint8ClampedArray(SharedArrayBuffer buffer, int byteOffset) : base(buffer, byteOffset) + { } + + public Uint8ClampedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) + { } + + internal Uint8ClampedArray(IntPtr js_handle) : base(js_handle) + { } + + /// + /// Defines an implicit conversion of JavaScript Core Uint8ClampedArray class to a Span<byte> + /// + public static implicit operator Span(Uint8ClampedArray typedarray) => typedarray.ToArray(); + + public static implicit operator Uint8ClampedArray(Span span) => From(span); + } +} diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/AssemblyInfo.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/AssemblyInfo.cs new file mode 100644 index 00000000000000..195efe1f21a663 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/AssemblyInfo.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; \ No newline at end of file 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 00000000000000..8fbb905857b794 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Tests.csproj @@ -0,0 +1,11 @@ + + + true + $(NetCoreAppCurrent);$(NetCoreAppCurrent)-Browser + true + + + + + + \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs new file mode 100644 index 00000000000000..9fe5ea566419ad --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Runtime.InteropServices.JavaScript.Tests +{ + public static class JavaScriptTests + { + } +}