File: System\Runtime\InteropServices\JavaScript\JSMarshalerArgument.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.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
using System.Threading;

namespace System.Runtime.InteropServices.JavaScript
{
    /// <summary>
    /// Contains the storage and type information for an argument or return value on the native stack.
    /// This API supports JSImport infrastructure and is not intended to be used directly from your code.
    /// </summary>
    [SupportedOSPlatform("browser")]
    [CLSCompliant(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public partial struct JSMarshalerArgument
    {
        internal JSMarshalerArgumentImpl slot;

        // keep in sync with JSMarshalerArgumentOffsets in marshal.ts
        [StructLayout(LayoutKind.Explicit, Pack = 32, Size = 32)]
        internal struct JSMarshalerArgumentImpl
        {
            [FieldOffset(0)]
            internal bool BooleanValue;  // note this is 1 byte
            [FieldOffset(0)]
            internal byte ByteValue;
            [FieldOffset(0)]
            internal char CharValue;
            [FieldOffset(0)]
            internal short Int16Value;
            [FieldOffset(0)]
            internal int Int32Value;
            [FieldOffset(0)]
            internal long Int64Value;// must be aligned to 8 because of HEAPI64 alignment
            [FieldOffset(0)]
            internal float SingleValue;
            [FieldOffset(0)]
            internal double DoubleValue;// must be aligned to 8 because of Module.HEAPF64 view alignment
            [FieldOffset(0)]
            internal IntPtr IntPtrValue;

            [FieldOffset(4)]
            internal IntPtr JSHandle;
            [FieldOffset(4)]
            internal IntPtr GCHandle;

            [FieldOffset(8)]
            internal int Length;

            /// <summary>
            /// Discriminators
            /// </summary>
            [FieldOffset(12)]
            internal MarshalerType Type;
            [FieldOffset(13)]
            internal MarshalerType ElementType;

            [FieldOffset(16)]
            internal IntPtr ContextHandle;

            [FieldOffset(20)]
            internal bool ReceiverShouldFree; // note this is 1 byte

            [FieldOffset(24)]
            internal IntPtr CallerNativeTID;

            [FieldOffset(28)]
            internal IntPtr SyncDoneSemaphorePtr;
        }

        /// <summary>
        /// This API supports JSImport infrastructure and is not intended to be used directly from your code.
        /// </summary>
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public void Initialize()
        {
            slot.Type = MarshalerType.None;
#if FEATURE_WASM_MANAGED_THREADS
            // we know that this is at the start of some JSImport call, but we don't know yet what would be the target thread
            // also this is called multiple times
            JSProxyContext.JSImportWithUnknownContext();
            slot.ContextHandle = IntPtr.Zero;
#endif
            slot.ReceiverShouldFree = false;
        }

#if FEATURE_WASM_MANAGED_THREADS
#if !DEBUG
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        internal unsafe void InitializeWithContext(JSProxyContext knownProxyContext)
        {
            slot.Type = MarshalerType.None;
            slot.ContextHandle = knownProxyContext.ContextHandle;
            slot.ReceiverShouldFree = false;
        }
#endif
        // this is always called from ToManaged() marshaler
#pragma warning disable CA1822 // Mark members as static
        internal JSProxyContext ToManagedContext
#pragma warning restore CA1822 // Mark members as static
        {
            get
            {
#if !FEATURE_WASM_MANAGED_THREADS
                return JSProxyContext.MainThreadContext;
#else
                // ContextHandle always has to be set
                // during JSImport, this is marshaling result/exception and it would be set by:
                //    - InvokeJSImport implementation
                //    - ActionJS.InvokeJS
                //    - ResolveVoidPromise/ResolvePromise/RejectPromise
                // during JSExport, this is marshaling parameters and it would be set by:
                //    - alloc_stack_frame
                //    - set_js_handle/set_gc_handle
                if (slot.ContextHandle == IntPtr.Zero)
                {
                    Environment.FailFast($"ContextHandle not set (ManagedThreadId {Environment.CurrentManagedThreadId}): {Environment.NewLine} {Environment.StackTrace}");
                }
                var proxyContextGCHandle = (GCHandle)slot.ContextHandle;
                var argumentContext = (JSProxyContext)proxyContextGCHandle.Target!;
                return argumentContext;
#endif
            }
        }

        // this is always called from ToJS() marshaler
#pragma warning disable CA1822 // Mark members as static
        internal JSProxyContext ToJSContext
#pragma warning restore CA1822 // Mark members as static
        {
            get
            {
#if !FEATURE_WASM_MANAGED_THREADS
                return JSProxyContext.MainThreadContext;
#else
                if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
                {
                    // we are called from ToJS, during JSImport
                    // we need to check for captured or default context
                    return JSProxyContext.CurrentOperationContext;
                }
                // ContextHandle must be set be set by JS side of JSExport, and we are marshaling result of JSExport
                var proxyContextGCHandle = slot.ContextHandle;
                if (proxyContextGCHandle == IntPtr.Zero)
                {
                    Environment.FailFast($"ContextHandle not set, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
                }
                var argumentContext = (JSProxyContext)((GCHandle)proxyContextGCHandle).Target!;
                return argumentContext;
#endif
            }
        }

        // make sure that we are on a thread with JS interop and that it matches the target of the argument
#pragma warning disable CA1822 // Mark members as static
        internal JSProxyContext AssertCurrentThreadContext()
#pragma warning restore CA1822 // Mark members as static
        {
#if !FEATURE_WASM_MANAGED_THREADS
            return JSProxyContext.MainThreadContext;
#else
            var currentThreadContext = JSProxyContext.CurrentThreadContext;
            if (currentThreadContext == null)
            {
                Environment.FailFast($"Must be called on same thread with JS interop, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
            }
            if (slot.ContextHandle != currentThreadContext.ContextHandle)
            {
                Environment.FailFast($"Must be called on same thread which created the stack frame, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
            }
            return currentThreadContext;
#endif
        }
    }
}