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

namespace System.Runtime.InteropServices.JavaScript
{
    /// <summary>
    /// Represents an exception initiated from the JavaScript interop code.
    /// </summary>
    [SupportedOSPlatform("browser")] // @kg: Do we really need to platform-lock JSException?
    public sealed class JSException : Exception
    {
        internal JSObject? jsException;
        internal string? combinedStackTrace;

        /// <summary>
        /// Initializes a new instance of the JSException class with a specified error message.
        /// </summary>
        /// <param name="msg">The message that describes the error.</param>
        public JSException(string msg) : base(msg)
        {
            jsException = null;
            combinedStackTrace = null;
        }

        internal JSException(string msg, JSObject? jsException) : base(msg)
        {
            this.jsException = jsException;
            this.combinedStackTrace = null;
        }

        /// <inheritdoc />
        public override string? StackTrace
        {
            get
            {
                if (combinedStackTrace != null)
                {
                    return combinedStackTrace;
                }
                var bs = base.StackTrace;
                if (jsException == null)
                {
                    return bs;
                }

#if FEATURE_WASM_MANAGED_THREADS
                if (!jsException.ProxyContext.IsCurrentThread())
                {
                    // if we are on another thread, it would be too expensive and risky to obtain lazy stack trace.
                    return bs + Environment.NewLine + "... omitted JavaScript stack trace from another thread.";
                }
#endif
                string? jsStackTrace = jsException.GetPropertyAsString("stack");

                // after this, we don't need jsException proxy anymore
                jsException.Dispose();
                jsException = null;

                if (jsStackTrace == null)
                {
                    combinedStackTrace = bs;
                    return combinedStackTrace;
                }
                else if (jsStackTrace.StartsWith(Message + "\n"))
                {
                    // Some JS runtimes insert the error message at the top of the stack, some don't,
                    // so normalize it by using the stack as the result if it already contains the error
                    jsStackTrace = jsStackTrace.Substring(Message.Length + 1);
                }

                if (bs == null)
                {
                    combinedStackTrace = jsStackTrace;
                }

                combinedStackTrace = bs != null
                    ? bs + Environment.NewLine + jsStackTrace
                    : jsStackTrace;

                return combinedStackTrace;
            }
        }

        /// <inheritdoc />
        public override bool Equals(object? obj)
        {
            return obj is JSException other && other.jsException == jsException;
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            return jsException == null
                ? base.GetHashCode()
                : base.GetHashCode() * jsException.GetHashCode();
        }

        /// <inheritdoc />
        public override string ToString()
        {
            // we avoid expensive stack trace
            return Message;
        }
    }
}