File: System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.Object.cs
Web Access
Project: src\src\runtime\src\libraries\System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj (System.Runtime.InteropServices.JavaScript)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace System.Runtime.InteropServices.JavaScript
{
    // Methods in this file are marshaling System.Object signature to Any JS signature dynamically.
    // In order to do that, we are referring to all well know marshaled types
    // therefore they could not be linked out during AOT, when user uses System.Object signature in his [JSImport] or [JSExport]
    // it is pay for play

    public partial struct JSMarshalerArgument
    {
        /// <summary>
        /// Implementation of the argument marshaling.
        /// It's used by JSImport code generator and should not be used by developers in source code.
        /// </summary>
        /// <param name="value">The value to be marshaled.</param>
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public void ToManaged(out object? value)
        {
            if (slot.Type == MarshalerType.None)
            {
                value = default;
            }
            else if (slot.Type == MarshalerType.Object)
            {
                value = ((GCHandle)slot.GCHandle).Target;
            }
            else if (slot.Type == MarshalerType.Boolean)
            {
                ToManaged(out bool v);
                value = v;
            }
            else if (slot.Type == MarshalerType.Double)
            {
                ToManaged(out double v);
                value = v;
            }
            else if (slot.Type == MarshalerType.Single)
            {
                ToManaged(out float v);
                value = v;
            }
            else if (slot.Type == MarshalerType.JSObject)
            {
                ToManaged(out JSObject? val);
                value = val;
            }
            else if (slot.Type == MarshalerType.String)
            {
                ToManaged(out string? val);
                value = val;
            }
            else if (slot.Type == MarshalerType.Exception)
            {
                ToManaged(out Exception? val);
                value = val;
            }
            else if (slot.Type == MarshalerType.DateTime)
            {
                ToManaged(out DateTime? val);
                value = val;
            }
            else if (slot.Type == MarshalerType.JSException)
            {
                ToManaged(out Exception? val);
                value = val;
            }
            else if (slot.Type == MarshalerType.Array)
            {
                if (slot.ElementType == MarshalerType.Byte)
                {
                    ToManaged(out byte[]? val);
                    value = val;
                }
                else if (slot.ElementType == MarshalerType.Double)
                {
                    ToManaged(out double[]? val);
                    value = val;
                }
                else if (slot.ElementType == MarshalerType.Single)
                {
                    ToManaged(out float[]? val);
                    value = val;
                }
                else if (slot.ElementType == MarshalerType.Int32)
                {
                    ToManaged(out int[]? val);
                    value = val;
                }
                else if (slot.ElementType == MarshalerType.Object)
                {
                    ToManaged(out object?[]? val);
                    value = val;
                }
                else
                {
                    throw new NotSupportedException(SR.Format(SR.ToManagedNotImplemented, slot.ElementType + "[]"));
                }
            }
            else if (slot.Type == MarshalerType.Task || slot.Type == MarshalerType.TaskResolved || slot.Type == MarshalerType.TaskRejected)
            {
                ToManaged(out Task<object?>? val, static (ref JSMarshalerArgument arg, out object? value) =>
                {
                    arg.ToManaged(out value);
                });
                value = val;
            }
            else
            {
                throw new NotSupportedException(SR.Format(SR.ToManagedNotImplemented, slot.Type));
            }
        }

        /// <summary>
        /// Implementation of the argument marshaling.
        /// It's used by JSImport code generator and should not be used by developers in source code.
        /// </summary>
        /// <param name="value">The value to be marshaled.</param>
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public void ToJS(object? value)
        {
            if (value == null)
            {
                slot.Type = MarshalerType.None;
                return;
            }

            Type type = value.GetType();
            if (type.IsPrimitive)
            {
                if (typeof(long) == type)
                {
                    // we do it because not all Int64 could fit into Int52 of the JS Number
                    throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
                }
                else if (typeof(int) == type)
                {
                    var v = (int)value;
                    ToJS(v);
                }
                else if (typeof(short) == type)
                {
                    var v = (short)value;
                    ToJS(v);
                }
                else if (typeof(byte) == type)
                {
                    var v = (byte)value;
                    ToJS(v);
                }
                else if (typeof(char) == type)
                {
                    var v = (char)value;
                    ToJS(v);
                }
                else if (typeof(bool) == type)
                {
                    var v = (bool)value;
                    ToJS(v);
                }
                else if (typeof(double) == type)
                {
                    var v = (double)value;
                    ToJS(v);
                }
                else if (typeof(float) == type)
                {
                    var v = (float)value;
                    ToJS(v);
                }
                else if (typeof(IntPtr) == type)
                {
                    var v = (IntPtr)value;
                    ToJS(v);
                }
                else
                {
                    throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
                }
            }
            else if (typeof(string) == type)
            {
                string? str = value as string;
                ToJS(str);
            }
            else if (typeof(DateTimeOffset) == type)
            {
                var v = (DateTimeOffset)value;
                ToJS(v);
            }
            else if (typeof(DateTime) == type)
            {
                var v = (DateTime)value;
                ToJS(v);
            }
            else if (Nullable.GetUnderlyingType(type) is Type ut && ut != null)
            {
                if (typeof(long) == ut)
                {
                    // we do it because not all Int64 could fit into Int52 of the JS Number
                    throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
                }
                else if (typeof(int) == ut)
                {
                    var nv = value as int?;
                    ToJS(nv);
                }
                else if (typeof(short) == ut)
                {
                    var nv = value as short?;
                    ToJS(nv);
                }
                else if (typeof(byte) == ut)
                {
                    var nv = value as byte?;
                    ToJS(nv);
                }
                else if (typeof(char) == ut)
                {
                    var nv = value as char?;
                    ToJS(nv);
                }
                else if (typeof(bool) == ut)
                {
                    var nv = value as bool?;
                    ToJS(nv);
                }
                else if (typeof(double) == ut)
                {
                    var nv = value as double?;
                    ToJS(nv);
                }
                else if (typeof(float) == ut)
                {
                    var nv = value as float?;
                    ToJS(nv);
                }
                else if (typeof(IntPtr) == ut)
                {
                    var nv = value as IntPtr?;
                    ToJS(nv);
                }
                else if (typeof(DateTimeOffset) == ut)
                {
                    var nv = value as DateTimeOffset?;
                    ToJS(nv);
                }
                else if (typeof(DateTime) == ut)
                {
                    var nv = value as DateTime?;
                    ToJS(nv);
                }
                else
                {
                    throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
                }
            }
            else if (typeof(JSObject).IsAssignableFrom(type))
            {
                JSObject? val = value as JSObject;
                ToJS(val);
            }
            else if (typeof(Exception).IsAssignableFrom(type))
            {
                Exception? val = value as Exception;
                ToJS(val);
            }
            else if (typeof(Task<object>) == type)
            {
                Task<object>? val = value as Task<object>;
                ToJS<object>(val, (ref JSMarshalerArgument arg, object value) =>
                {
                    object? valueRef = value;
                    arg.ToJS(valueRef);
                });
            }
            else if (typeof(Task).IsAssignableFrom(type))
            {
                Task? val = value as Task;
                ToJSDynamic(val);
            }
            else if (typeof(byte[]) == type)
            {
                byte[] val = (byte[])value;
                ToJS(val);
            }
            else if (typeof(int[]) == type)
            {
                int[] val = (int[])value;
                ToJS(val);
            }
            else if (typeof(float[]) == type)
            {
                float[] val = (float[])value;
                ToJS(val);
            }
            else if (typeof(double[]) == type)
            {
                double[] val = (double[])value;
                ToJS(val);
            }
            else if (typeof(string[]) == type)
            {
                string[] val = (string[])value;
                ToJS(val);
            }
            else if (typeof(object[]) == type)
            {
                object[] val = (object[])value;
                ToJS(val);
            }
            else if (type.IsArray)
            {
                throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
            }
            else if (typeof(MulticastDelegate).IsAssignableFrom(type.BaseType))
            {
                throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ArraySegment<>))
            {
                throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Span<>))
            {
                throw new NotSupportedException(SR.Format(SR.ToJSNotImplemented, type.FullName));
            }
            else
            {
                slot.Type = MarshalerType.Object;
                var ctx = ToJSContext;
                slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value);
            }
        }

        /// <summary>
        /// Implementation of the argument marshaling.
        /// It's used by JSImport code generator and should not be used by developers in source code.
        /// </summary>
        /// <param name="value">The value to be marshaled.</param>
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public unsafe void ToManaged(out object?[]? value)
        {
            if (slot.Type == MarshalerType.None)
            {
                value = null;
                return;
            }

            value = new object?[slot.Length];
            JSMarshalerArgument* payload = (JSMarshalerArgument*)slot.IntPtrValue;
            for (int i = 0; i < slot.Length; i++)
            {
                ref JSMarshalerArgument arg = ref payload[i];
                object? val;
                arg.ToManaged(out val);
                value[i] = val;
            }
#if !ENABLE_JS_INTEROP_BY_VALUE
            Interop.Runtime.DeregisterGCRoot(slot.IntPtrValue);
#endif
            NativeMemory.Free((void*)slot.IntPtrValue);
        }

        /// <summary>
        /// Implementation of the argument marshaling.
        /// It's used by JSImport code generator and should not be used by developers in source code.
        /// </summary>
        /// <param name="value">The value to be marshaled.</param>
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public unsafe void ToJS(object?[] value)
        {
            if (value == null)
            {
                slot.Type = MarshalerType.None;
                return;
            }
            slot.Length = value.Length;
            int bytes = value.Length * sizeof(JSMarshalerArgument);
            slot.Type = MarshalerType.Array;
            JSMarshalerArgument* payload = (JSMarshalerArgument*)NativeMemory.Alloc((nuint)bytes);
            Unsafe.InitBlock(payload, 0, (uint)bytes);
#if !ENABLE_JS_INTEROP_BY_VALUE
            Interop.Runtime.RegisterGCRoot(payload, bytes, IntPtr.Zero);
#endif
            for (int i = 0; i < slot.Length; i++)
            {
                ref JSMarshalerArgument arg = ref payload[i];
                object? val = value[i];
                arg.ToJS(val);
                value[i] = val;
            }
            slot.ElementType = MarshalerType.Object;
            slot.IntPtrValue = (IntPtr)payload;
        }
    }
}