From 2c3fb20d328c93a255352c9aea18d9439006527a Mon Sep 17 00:00:00 2001 From: Jonathan Close Date: Tue, 1 Sep 2026 14:47:55 +0100 Subject: [PATCH] Add V8 promise rejection monitoring --- .../V8/SplitProxy/IV8SplitProxyNative.cs | 2 +- .../V8/SplitProxy/V8ContextProxyImpl.cs | 10 +- .../V8/SplitProxy/V8IsolateProxyImpl.cs | 4 +- .../V8/SplitProxy/V8SplitProxyManaged.cs | 18 +++ .../SplitProxy/V8SplitProxyNative.Common.tt | 7 +- .../V8SplitProxyNative.Generated.cs | 56 ++++--- .../V8SplitProxyNative.UWP.Generated.cs | 21 +-- ClearScript/V8/V8ContextProxy.cs | 4 +- ClearScript/V8/V8PromiseRejectionEventArgs.cs | 51 +++++++ ClearScript/V8/V8ScriptEngine.cs | 29 +++- ClearScriptTest/V8ScriptEngineTest.cs | 133 +++++++++++++++++ ClearScriptV8/HostObjectUtil.cpp | 7 + ClearScriptV8/HostObjectUtil.h | 1 + ClearScriptV8/V8Context.h | 1 + ClearScriptV8/V8ContextImpl.cpp | 25 +++- ClearScriptV8/V8ContextImpl.h | 2 + ClearScriptV8/V8IsolateImpl.cpp | 138 ++++++++++++++++++ ClearScriptV8/V8IsolateImpl.h | 32 ++++ ClearScriptV8/V8SplitProxyManaged.h | 1 + ClearScriptV8/V8SplitProxyNative.cpp | 12 +- ClearScriptV8/V8SplitProxyNative.h | 2 +- NetCore/ClearScript.V8/ClearScript.V8.csproj | 1 + .../ClearScript.V8/ClearScript.V8.csproj | 1 + .../ClearScript.V8/ClearScript.V8.csproj | 1 + UWP/ClearScript.V8/ClearScript.V8.csproj | 1 + 25 files changed, 512 insertions(+), 48 deletions(-) create mode 100644 ClearScript/V8/V8PromiseRejectionEventArgs.cs diff --git a/ClearScript/V8/SplitProxy/IV8SplitProxyNative.cs b/ClearScript/V8/SplitProxy/IV8SplitProxyNative.cs index eaf4756e..304150c6 100644 --- a/ClearScript/V8/SplitProxy/IV8SplitProxyNative.cs +++ b/ClearScript/V8/SplitProxy/IV8SplitProxyNative.cs @@ -136,7 +136,7 @@ internal interface IV8SplitProxyNative #region V8 isolate methods V8Isolate.Handle V8Isolate_Create(string name, int maxNewSpaceSize, int maxOldSpaceSize, double heapExpansionMultiplier, ulong maxArrayBufferAllocation, V8RuntimeFlags flags, int debugPort); - V8Context.Handle V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort); + V8Context.Handle V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback); UIntPtr V8Isolate_GetMaxHeapSize(V8Isolate.Handle hIsolate); void V8Isolate_SetMaxHeapSize(V8Isolate.Handle hIsolate, UIntPtr size); double V8Isolate_GetHeapSizeSampleInterval(V8Isolate.Handle hIsolate); diff --git a/ClearScript/V8/SplitProxy/V8ContextProxyImpl.cs b/ClearScript/V8/SplitProxy/V8ContextProxyImpl.cs index 5a75a89b..34e64277 100644 --- a/ClearScript/V8/SplitProxy/V8ContextProxyImpl.cs +++ b/ClearScript/V8/SplitProxy/V8ContextProxyImpl.cs @@ -14,9 +14,15 @@ internal sealed class V8ContextProxyImpl : V8ContextProxy private V8Context.Handle Handle => (V8Context.Handle)holder.Handle; - public V8ContextProxyImpl(V8IsolateProxy isolateProxy, string name, V8ScriptEngineFlags flags, int debugPort) + public V8ContextProxyImpl(V8IsolateProxy isolateProxy, string name, V8ScriptEngineFlags flags, int debugPort, Action promiseRejectionCallback) { - holder = new V8EntityHolder("V8 script engine", () => ((V8IsolateProxyImpl)isolateProxy).CreateContext(name, flags, debugPort)); + holder = new V8EntityHolder("V8 script engine", () => + { + using (var callbackScope = V8ProxyHelpers.CreateAddRefHostObjectScope(promiseRejectionCallback)) + { + return ((V8IsolateProxyImpl)isolateProxy).CreateContext(name, flags, debugPort, callbackScope.Value); + } + }); } #region V8ContextProxy overrides diff --git a/ClearScript/V8/SplitProxy/V8IsolateProxyImpl.cs b/ClearScript/V8/SplitProxy/V8IsolateProxyImpl.cs index c6dcfea5..62bd09ce 100644 --- a/ClearScript/V8/SplitProxy/V8IsolateProxyImpl.cs +++ b/ClearScript/V8/SplitProxy/V8IsolateProxyImpl.cs @@ -29,9 +29,9 @@ public V8IsolateProxyImpl(string name, V8RuntimeConstraints constraints, V8Runti )); } - public V8Context.Handle CreateContext(string name, V8ScriptEngineFlags flags, int debugPort) + public V8Context.Handle CreateContext(string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { - return V8SplitProxyNative.Invoke(static (instance, ctx) => instance.V8Isolate_CreateContext(ctx.Handle, ctx.name, ctx.flags, ctx.debugPort), (Handle, name, flags, debugPort)); + return V8SplitProxyNative.Invoke(static (instance, ctx) => instance.V8Isolate_CreateContext(ctx.Handle, ctx.name, ctx.flags, ctx.debugPort, ctx.pPromiseRejectionCallback), (Handle, name, flags, debugPort, pPromiseRejectionCallback)); } #region V8IsolateProxy overrides diff --git a/ClearScript/V8/SplitProxy/V8SplitProxyManaged.cs b/ClearScript/V8/SplitProxy/V8SplitProxyManaged.cs index c3e4c5ba..98b45192 100644 --- a/ClearScript/V8/SplitProxy/V8SplitProxyManaged.cs +++ b/ClearScript/V8/SplitProxy/V8SplitProxyManaged.cs @@ -385,6 +385,14 @@ private delegate void RawQueueNativeCallback( [In] NativeCallback.Handle hCallback ); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate void RawNotifyPromiseRejection( + [In] IntPtr pCallback, + [In] int operation, + [In] V8Value.Ptr pPromise, + [In] V8Value.Ptr pReason + ); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate IntPtr RawCreateNativeCallbackTimer( [In] int dueTime, @@ -566,6 +574,7 @@ private static void CreateMethodTable() GetMethodPair(AsyncDisposeFastHostObject), GetMethodPair(QueueNativeCallback), + GetMethodPair(NotifyPromiseRejection), GetMethodPair(CreateNativeCallbackTimer), GetMethodPair(ChangeNativeCallbackTimer), GetMethodPair(DestroyNativeCallbackTimer), @@ -1170,6 +1179,15 @@ private static void QueueNativeCallback(NativeCallback.Handle hCallback) MiscHelpers.QueueNativeCallback(new NativeCallbackImpl(hCallback)); } + private static void NotifyPromiseRejection(IntPtr pCallback, int operation, V8Value.Ptr pPromise, V8Value.Ptr pReason) + { + MiscHelpers.Try(() => V8ProxyHelpers.GetHostObject>(pCallback)( + (V8PromiseRejectionOperation)operation, + V8Value.Get(pPromise), + V8Value.Get(pReason) + )); + } + private static IntPtr CreateNativeCallbackTimer(int dueTime, int period, NativeCallback.Handle hCallback) { return V8ProxyHelpers.AddRefHostObject(new NativeCallbackTimer(dueTime, period, new NativeCallbackImpl(hCallback))); diff --git a/ClearScript/V8/SplitProxy/V8SplitProxyNative.Common.tt b/ClearScript/V8/SplitProxy/V8SplitProxyNative.Common.tt index aa1bff5b..7182843a 100644 --- a/ClearScript/V8/SplitProxy/V8SplitProxyNative.Common.tt +++ b/ClearScript/V8/SplitProxy/V8SplitProxyNative.Common.tt @@ -476,11 +476,11 @@ namespace Microsoft.ClearScript.V8.SplitProxy } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -1620,7 +1620,8 @@ namespace Microsoft.ClearScript.V8.SplitProxy [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("<#= fileName #>", CallingConvention = CallingConvention.StdCall)] diff --git a/ClearScript/V8/SplitProxy/V8SplitProxyNative.Generated.cs b/ClearScript/V8/SplitProxy/V8SplitProxyNative.Generated.cs index adef5904..0a99d7ca 100644 --- a/ClearScript/V8/SplitProxy/V8SplitProxyNative.Generated.cs +++ b/ClearScript/V8/SplitProxy/V8SplitProxyNative.Generated.cs @@ -518,11 +518,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -1662,7 +1662,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-x86.dll", CallingConvention = CallingConvention.StdCall)] @@ -2725,11 +2726,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -3869,7 +3870,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-x64.dll", CallingConvention = CallingConvention.StdCall)] @@ -4932,11 +4934,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -6076,7 +6078,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-arm64.dll", CallingConvention = CallingConvention.StdCall)] @@ -7139,11 +7142,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -8283,7 +8286,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.linux-x64.so", CallingConvention = CallingConvention.StdCall)] @@ -9346,11 +9350,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -10490,7 +10494,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.linux-arm64.so", CallingConvention = CallingConvention.StdCall)] @@ -11553,11 +11558,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -12697,7 +12702,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.linux-arm.so", CallingConvention = CallingConvention.StdCall)] @@ -13760,11 +13766,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -14904,7 +14910,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.osx-x64.dylib", CallingConvention = CallingConvention.StdCall)] @@ -15967,11 +15974,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -17111,7 +17118,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.osx-arm64.dylib", CallingConvention = CallingConvention.StdCall)] diff --git a/ClearScript/V8/SplitProxy/V8SplitProxyNative.UWP.Generated.cs b/ClearScript/V8/SplitProxy/V8SplitProxyNative.UWP.Generated.cs index 44a7820e..0657d257 100644 --- a/ClearScript/V8/SplitProxy/V8SplitProxyNative.UWP.Generated.cs +++ b/ClearScript/V8/SplitProxy/V8SplitProxyNative.UWP.Generated.cs @@ -474,11 +474,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -1618,7 +1618,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-x86.dll", CallingConvention = CallingConvention.StdCall)] @@ -2681,11 +2682,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -3825,7 +3826,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-x64.dll", CallingConvention = CallingConvention.StdCall)] @@ -4888,11 +4890,11 @@ V8Isolate.Handle IV8SplitProxyNative.V8Isolate_Create(string name, int maxNewSpa } } - V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort) + V8Context.Handle IV8SplitProxyNative.V8Isolate_CreateContext(V8Isolate.Handle hIsolate, string name, V8ScriptEngineFlags flags, int debugPort, IntPtr pPromiseRejectionCallback) { using (var nameScope = StdString.CreateScope(name)) { - return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort); + return V8Isolate_CreateContext(hIsolate, nameScope.Value, flags, debugPort, pPromiseRejectionCallback); } } @@ -6032,7 +6034,8 @@ private static extern V8Context.Handle V8Isolate_CreateContext( [In] V8Isolate.Handle hIsolate, [In] StdString.Ptr pName, [In] V8ScriptEngineFlags flags, - [In] int debugPort + [In] int debugPort, + [In] IntPtr pPromiseRejectionCallback ); [DllImport("ClearScriptV8.win-arm64.dll", CallingConvention = CallingConvention.StdCall)] diff --git a/ClearScript/V8/V8ContextProxy.cs b/ClearScript/V8/V8ContextProxy.cs index ebeae064..4bbfba8a 100644 --- a/ClearScript/V8/V8ContextProxy.cs +++ b/ClearScript/V8/V8ContextProxy.cs @@ -9,9 +9,9 @@ namespace Microsoft.ClearScript.V8 { internal abstract class V8ContextProxy : V8Proxy { - public static V8ContextProxy Create(V8IsolateProxy isolateProxy, string name, V8ScriptEngineFlags flags, int debugPort) + public static V8ContextProxy Create(V8IsolateProxy isolateProxy, string name, V8ScriptEngineFlags flags, int debugPort, Action promiseRejectionCallback) { - return new V8ContextProxyImpl(isolateProxy, name, flags, debugPort); + return new V8ContextProxyImpl(isolateProxy, name, flags, debugPort, promiseRejectionCallback); } public abstract UIntPtr MaxIsolateHeapSize { get; set; } diff --git a/ClearScript/V8/V8PromiseRejectionEventArgs.cs b/ClearScript/V8/V8PromiseRejectionEventArgs.cs new file mode 100644 index 00000000..dbcc13f9 --- /dev/null +++ b/ClearScript/V8/V8PromiseRejectionEventArgs.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.ClearScript.V8 +{ + /// + /// Identifies a V8 promise rejection lifecycle operation. + /// + public enum V8PromiseRejectionOperation + { + /// + /// A promise was rejected without a handler. + /// + RejectWithNoHandler, + + /// + /// A handler was added to a previously rejected promise. + /// + HandlerAddedAfterReject + } + + /// + /// Provides data for V8 promise rejection lifecycle notifications. + /// + public sealed class V8PromiseRejectionEventArgs : EventArgs + { + /// + /// Gets the promise rejection lifecycle operation. + /// + public V8PromiseRejectionOperation Operation { get; } + + /// + /// Gets the original JavaScript promise. + /// + public ScriptObject Promise { get; } + + /// + /// Gets the promise rejection reason. This value can be any JavaScript value. + /// + public object Reason { get; } + + internal V8PromiseRejectionEventArgs(V8PromiseRejectionOperation operation, ScriptObject promise, object reason) + { + Operation = operation; + Promise = promise; + Reason = reason; + } + } +} diff --git a/ClearScript/V8/V8ScriptEngine.cs b/ClearScript/V8/V8ScriptEngine.cs index f15a4e44..32aec33c 100644 --- a/ClearScript/V8/V8ScriptEngine.cs +++ b/ClearScript/V8/V8ScriptEngine.cs @@ -232,7 +232,7 @@ internal V8ScriptEngine(V8Runtime runtime, string name, V8RuntimeConstraints con HostItemCollateral = runtime.HostItemCollateral; Flags = flags; - proxy = V8ContextProxy.Create(runtime.IsolateProxy, Name, flags, debugPort); + proxy = V8ContextProxy.Create(runtime.IsolateProxy, Name, flags, debugPort, OnPromiseRejection); script = (V8ScriptItem)GetRootItem(); if (flags.HasAllFlags(V8ScriptEngineFlags.EnableStringifyEnhancements)) @@ -252,6 +252,21 @@ internal V8ScriptEngine(V8Runtime runtime, string name, V8RuntimeConstraints con #region public members + /// + /// Occurs when V8 reports a promise rejection lifecycle notification for this script engine. + /// + /// + /// + /// The event reports V8's embedder-level rejection lifecycle. It does not implement browser + /// unhandled-rejection checkpoint semantics. + /// + /// + /// Notifications are delivered in order after the active V8 callback and microtask work have + /// unwound. Delivery is serialized with script execution for the containing V8 runtime. + /// + /// + public event EventHandler PromiseRejection; + /// /// Resumes script execution if the script engine is waiting for a debugger connection. /// @@ -1935,6 +1950,18 @@ internal override TResult ScriptInvoke(Func func, #endregion + private void OnPromiseRejection(V8PromiseRejectionOperation operation, object promise, object reason) + { + if (!disposedFlag.IsSet) + { + var scriptPromise = MarshalToHost(promise, false) as ScriptObject; + if (scriptPromise is not null) + { + PromiseRejection?.Invoke(this, new V8PromiseRejectionEventArgs(operation, scriptPromise, MarshalToHost(reason, false))); + } + } + } + #region ScriptEngine overrides (disposal / finalization) /// diff --git a/ClearScriptTest/V8ScriptEngineTest.cs b/ClearScriptTest/V8ScriptEngineTest.cs index 92562e72..1890c27c 100644 --- a/ClearScriptTest/V8ScriptEngineTest.cs +++ b/ClearScriptTest/V8ScriptEngineTest.cs @@ -59,6 +59,139 @@ public void TestCleanup() // ReSharper disable InconsistentNaming + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_RejectWithNoHandler() + { + var notifications = new List(); + engine.PromiseRejection += (_, args) => notifications.Add(args); + + engine.Execute("promise = Promise.reject('failure'); isOriginalPromise = value => value === promise"); + + Assert.AreEqual(1, notifications.Count); + Assert.AreEqual(V8PromiseRejectionOperation.RejectWithNoHandler, notifications[0].Operation); + Assert.IsTrue((bool)engine.Script.isOriginalPromise(notifications[0].Promise)); + Assert.AreEqual("failure", notifications[0].Reason); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_HandledImmediately() + { + var notifications = new List(); + engine.PromiseRejection += (_, args) => notifications.Add(args); + + engine.Execute("promise = Promise.reject('failure'); promise.catch(() => {}); samePromise = (left, right) => left === right"); + + Assert.AreEqual(2, notifications.Count); + Assert.AreEqual(V8PromiseRejectionOperation.RejectWithNoHandler, notifications[0].Operation); + Assert.AreEqual(V8PromiseRejectionOperation.HandlerAddedAfterReject, notifications[1].Operation); + Assert.IsTrue((bool)engine.Script.samePromise(notifications[0].Promise, notifications[1].Promise)); + Assert.AreEqual("failure", notifications[1].Reason); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_HandlerAddedAfterReject() + { + var notifications = new List(); + engine.PromiseRejection += (_, args) => notifications.Add(args); + + engine.Execute("promise = Promise.reject(123); samePromise = (left, right) => left === right"); + Assert.AreEqual(1, notifications.Count); + + engine.Execute("promise.catch(() => {})"); + + Assert.AreEqual(2, notifications.Count); + Assert.AreEqual(V8PromiseRejectionOperation.RejectWithNoHandler, notifications[0].Operation); + Assert.AreEqual(V8PromiseRejectionOperation.HandlerAddedAfterReject, notifications[1].Operation); + Assert.IsTrue((bool)engine.Script.samePromise(notifications[0].Promise, notifications[1].Promise)); + Assert.AreEqual(123, notifications[1].Reason); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_AsyncFunction() + { + V8PromiseRejectionEventArgs notification = null; + engine.PromiseRejection += (_, args) => notification = args; + + engine.Execute("stringify = value => String(value); void (async () => { throw new Error('async failure'); })()"); + + Assert.IsNotNull(notification); + Assert.AreEqual(V8PromiseRejectionOperation.RejectWithNoHandler, notification.Operation); + Assert.AreEqual("Error: async failure", engine.Script.stringify(notification.Reason)); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_PromiseIdentity() + { + V8PromiseRejectionEventArgs notification = null; + engine.PromiseRejection += (_, args) => notification = args; + + engine.Execute("promise = Promise.reject('failure'); validatePromise = value => (value instanceof Promise) && (value === promise)"); + + Assert.IsTrue((bool)engine.Script.validatePromise(notification.Promise)); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_Reasons() + { + var notifications = new List(); + engine.PromiseRejection += (_, args) => notifications.Add(args); + + engine.Execute("objectReason = { value: 456 }; isObjectReason = value => value === objectReason; Promise.reject(123); Promise.reject(objectReason)"); + + Assert.AreEqual(2, notifications.Count); + Assert.AreEqual(123, notifications[0].Reason); + Assert.IsTrue((bool)engine.Script.isObjectReason(notifications[1].Reason)); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_SharedRuntimeRouting() + { + using (var runtime = new V8Runtime()) + using (var engine1 = runtime.CreateScriptEngine()) + using (var engine2 = runtime.CreateScriptEngine()) + { + var notifications1 = new List(); + var notifications2 = new List(); + engine1.PromiseRejection += (_, args) => notifications1.Add(args); + engine2.PromiseRejection += (_, args) => notifications2.Add(args); + + engine1.Execute("Promise.reject('engine 1')"); + Assert.AreEqual(1, notifications1.Count); + Assert.AreEqual(0, notifications2.Count); + + engine2.Execute("Promise.reject('engine 2')"); + Assert.AreEqual(1, notifications1.Count); + Assert.AreEqual(1, notifications2.Count); + Assert.AreEqual("engine 1", notifications1[0].Reason); + Assert.AreEqual("engine 2", notifications2[0].Reason); + } + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_DisposalWithPendingTask() + { + var source = new TaskCompletionSource(); + + using (var runtime = new V8Runtime()) + { + var tempEngine = runtime.CreateScriptEngine(V8ScriptEngineFlags.EnableTaskPromiseConversion); + tempEngine.AddHostObject("getTask", new Func>(() => source.Task)); + tempEngine.Execute("void getTask()"); + + source.SetException(new InvalidOperationException("task failure")); + tempEngine.Dispose(); + } + + Thread.Sleep(100); + } + + [TestMethod, TestCategory("V8ScriptEngine")] + public void V8ScriptEngine_PromiseRejection_NoSubscriber() + { + engine.Execute("for (let index = 0; index < 100; index++) Promise.reject(index)"); + engine.CollectGarbage(true); + } + [TestMethod, TestCategory("V8ScriptEngine")] public void V8ScriptEngine_AddHostObject() { diff --git a/ClearScriptV8/HostObjectUtil.cpp b/ClearScriptV8/HostObjectUtil.cpp index bdf3f47c..59b8132d 100644 --- a/ClearScriptV8/HostObjectUtil.cpp +++ b/ClearScriptV8/HostObjectUtil.cpp @@ -265,6 +265,13 @@ void HostObjectUtil::QueueNativeCallback(NativeCallback&& callback) //----------------------------------------------------------------------------- +void HostObjectUtil::NotifyPromiseRejection(void* pvCallback, int32_t operation, const V8Value& promise, const V8Value& reason) +{ + V8_SPLIT_PROXY_MANAGED_INVOKE_VOID_NOTHROW(NotifyPromiseRejection, pvCallback, operation, promise, reason); +} + +//----------------------------------------------------------------------------- + void* HostObjectUtil::CreateNativeCallbackTimer(int32_t dueTime, int32_t period, NativeCallback&& callback) { return V8_SPLIT_PROXY_MANAGED_INVOKE_NOTHROW(void*, CreateNativeCallbackTimer, dueTime, period, new NativeCallbackHandle(new NativeCallback(std::move(callback)))); diff --git a/ClearScriptV8/HostObjectUtil.h b/ClearScriptV8/HostObjectUtil.h index 10602509..9c68793b 100644 --- a/ClearScriptV8/HostObjectUtil.h +++ b/ClearScriptV8/HostObjectUtil.h @@ -66,6 +66,7 @@ struct HostObjectUtil final: StaticBase using NativeCallback = std::function; static void QueueNativeCallback(NativeCallback&& callback); + static void NotifyPromiseRejection(void* pvCallback, int32_t operation, const V8Value& promise, const V8Value& reason); static void* CreateNativeCallbackTimer(int32_t dueTime, int32_t period, NativeCallback&& callback); static bool ChangeNativeCallbackTimer(void* pvTimer, int32_t dueTime, int32_t period); static void DestroyNativeCallbackTimer(void* pvTimer); diff --git a/ClearScriptV8/V8Context.h b/ClearScriptV8/V8Context.h index 949cbca1..66b1ac5d 100644 --- a/ClearScriptV8/V8Context.h +++ b/ClearScriptV8/V8Context.h @@ -38,6 +38,7 @@ class V8Context: public WeakRefTarget, public IV8Entity { Flags Flags = Flags::None; int DebugPort = 0; + void* pvPromiseRejectionCallback = nullptr; }; struct Statistics final diff --git a/ClearScriptV8/V8ContextImpl.cpp b/ClearScriptV8/V8ContextImpl.cpp index 8ae90e37..36e2cf5e 100644 --- a/ClearScriptV8/V8ContextImpl.cpp +++ b/ClearScriptV8/V8ContextImpl.cpp @@ -389,7 +389,8 @@ V8ContextImpl::V8ContextImpl(SharedPtr&& spIsolateImpl, const Std m_HideHostExceptions(::HasFlag(options.Flags, Flags::HideHostExceptions)), m_AllowHostObjectConstructorCall(false), m_ChangedTimerResolution(false), - m_pvV8ObjectCache(nullptr) + m_pvV8ObjectCache(nullptr), + m_pvPromiseRejectionCallback(options.pvPromiseRejectionCallback) { VerifyNotOutOfMemory(); @@ -607,6 +608,22 @@ size_t V8ContextImpl::GetInstanceCount() //----------------------------------------------------------------------------- +void V8ContextImpl::NotifyPromiseRejection(int32_t operation, v8::Local hPromise, v8::Local hReason) +{ + if (m_pvPromiseRejectionCallback != nullptr) + { + BEGIN_CONTEXT_SCOPE + + auto promise = ExportValue(hPromise); + auto reason = hReason.IsEmpty() ? V8Value(V8Value::Undefined) : ExportValue(hReason); + HostObjectUtil::NotifyPromiseRejection(m_pvPromiseRejectionCallback, operation, promise, reason); + + END_CONTEXT_SCOPE + } +} + +//----------------------------------------------------------------------------- + size_t V8ContextImpl::GetMaxIsolateHeapSize() { return m_spIsolateImpl->GetMaxHeapSize(); @@ -2221,6 +2238,12 @@ void V8ContextImpl::Teardown() m_spIsolateImpl->RemoveContext(this); + if (m_pvPromiseRejectionCallback != nullptr) + { + HostObjectUtil::Release(m_pvPromiseRejectionCallback); + m_pvPromiseRejectionCallback = nullptr; + } + if (m_pvV8ObjectCache != nullptr) { std::vector v8ObjectPtrs; diff --git a/ClearScriptV8/V8ContextImpl.h b/ClearScriptV8/V8ContextImpl.h index 335bfe70..8192e4f4 100644 --- a/ClearScriptV8/V8ContextImpl.h +++ b/ClearScriptV8/V8ContextImpl.h @@ -25,6 +25,7 @@ class V8ContextImpl final: public V8Context const StdString& GetName() const { return m_Name; } const Persistent& GetContext() const { return m_hContext; } + void NotifyPromiseRejection(int32_t operation, v8::Local hPromise, v8::Local hReason); virtual size_t GetMaxIsolateHeapSize() override; virtual void SetMaxIsolateHeapSize(size_t value) override; @@ -574,6 +575,7 @@ class V8ContextImpl final: public V8Context bool m_AllowHostObjectConstructorCall; bool m_ChangedTimerResolution; void* m_pvV8ObjectCache; + void* m_pvPromiseRejectionCallback; double m_RelativeTimeOrigin; }; diff --git a/ClearScriptV8/V8IsolateImpl.cpp b/ClearScriptV8/V8IsolateImpl.cpp index 58e9fba8..ae7eda81 100644 --- a/ClearScriptV8/V8IsolateImpl.cpp +++ b/ClearScriptV8/V8IsolateImpl.cpp @@ -476,6 +476,7 @@ static size_t* const s_pMinStackLimit = reinterpret_cast(sizeof(size_t) V8IsolateImpl::V8IsolateImpl(const StdString& name, const v8::ResourceConstraints* pConstraints, const Options& options): m_Name(name), m_CallWithLockLevel(0), + m_PromiseRejectionNotificationPending(false), m_DebuggingEnabled(false), m_MaxArrayBufferAllocation(options.MaxArrayBufferAllocation), m_ArrayBufferAllocation(0), @@ -514,6 +515,7 @@ V8IsolateImpl::V8IsolateImpl(const StdString& name, const v8::ResourceConstraint m_upIsolate->AddNearHeapLimitCallback(HeapExpansionCallback, this); m_upIsolate->AddBeforeCallEnteredCallback(OnBeforeCallEntered); + m_upIsolate->SetPromiseRejectCallback(PromiseRejectCallback); BEGIN_ISOLATE_SCOPE @@ -1641,6 +1643,13 @@ V8IsolateImpl::~V8IsolateImpl() BEGIN_ISOLATE_SCOPE DisableDebugging(); ClearScriptCache(); + + while (!m_PromiseRejectionQueue.empty()) + { + m_PromiseRejectionQueue.pop(); + } + + m_PromiseRejectionNotificationPending = false; END_ISOLATE_SCOPE { @@ -2191,6 +2200,135 @@ void V8IsolateImpl::PromiseHook(v8::PromiseHookType type, v8::Local //----------------------------------------------------------------------------- +void V8IsolateImpl::PromiseRejectCallback(v8::PromiseRejectMessage message) +{ + try + { + int32_t operation; + switch (message.GetEvent()) + { + case v8::PromiseRejectEvent::kPromiseRejectWithNoHandler: + operation = 0; + break; + + case v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject: + operation = 1; + break; + + default: + return; + } + + auto hPromise = message.GetPromise(); + if (hPromise.IsEmpty()) + { + return; + } + + auto pIsolate = v8::Isolate::GetCurrent(); + auto pIsolateImpl = GetInstanceFromIsolate(pIsolate); + auto hContext = hPromise->GetCreationContext(pIsolate).FromMaybe(v8::Local()); + if (hContext.IsEmpty()) + { + return; + } + + auto pContextImpl = pIsolateImpl->FindContext(hContext); + if (pContextImpl == nullptr) + { + return; + } + + auto hReason = message.GetValue(); + if (hReason.IsEmpty() && (hPromise->State() == v8::Promise::PromiseState::kRejected)) + { + hReason = hPromise->Result(); + } + + pIsolateImpl->QueuePromiseRejection(*pContextImpl, operation, hPromise, hReason); + } + catch (...) + { + } +} + +//----------------------------------------------------------------------------- + +void V8IsolateImpl::QueuePromiseRejection(V8ContextImpl& contextImpl, int32_t operation, v8::Local hPromise, v8::Local hReason) +{ + _ASSERTE(IsCurrent() && IsLocked()); + + auto upEntry = std::make_unique( + contextImpl.CreateWeakRef(), + operation, + Persistent(), + Persistent() + ); + + upEntry->hPromise = CreatePersistent(hPromise); + if (!hReason.IsEmpty()) + { + upEntry->hReason = CreatePersistent(hReason); + } + + m_PromiseRejectionQueue.push(std::move(upEntry)); + + if (!m_PromiseRejectionNotificationPending) + { + m_PromiseRejectionNotificationPending = true; + try + { + CallWithLockAsync(true, [] (V8IsolateImpl* pIsolateImpl) + { + pIsolateImpl->ProcessPromiseRejectionQueue(); + }); + } + catch (...) + { + while (!m_PromiseRejectionQueue.empty()) + { + m_PromiseRejectionQueue.pop(); + } + + m_PromiseRejectionNotificationPending = false; + throw; + } + } +} + +//----------------------------------------------------------------------------- + +void V8IsolateImpl::ProcessPromiseRejectionQueue() +{ + _ASSERTE(IsCurrent() && IsLocked()); + + while (!m_PromiseRejectionQueue.empty()) + { + auto upEntry = std::move(m_PromiseRejectionQueue.front()); + m_PromiseRejectionQueue.pop(); + + auto spContext = upEntry->wrContext.GetTarget(); + if (!spContext.IsEmpty()) + { + try + { + spContext.DerefAs().NotifyPromiseRejection( + upEntry->Operation, + CreateLocal(upEntry->hPromise), + !upEntry->hReason.IsEmpty() ? CreateLocal(upEntry->hReason) : v8::Local() + ); + } + catch (...) + { + } + } + } + + m_PromiseRejectionNotificationPending = false; +} + +//----------------------------------------------------------------------------- + void V8IsolateImpl::FlushContextAsync(v8::Local hContext) { _ASSERTE(IsCurrent() && IsLocked()); diff --git a/ClearScriptV8/V8IsolateImpl.h b/ClearScriptV8/V8IsolateImpl.h index 175539a3..e0bf6c38 100644 --- a/ClearScriptV8/V8IsolateImpl.h +++ b/ClearScriptV8/V8IsolateImpl.h @@ -607,6 +607,32 @@ class V8IsolateImpl final: public V8Isolate, public v8_inspector::V8InspectorCli } }; + struct PromiseRejectionEntry final + { + PROHIBIT_COPY(PromiseRejectionEntry) + + WeakRef wrContext; + int32_t Operation; + Persistent hPromise; + Persistent hReason; + + PromiseRejectionEntry(const WeakRef& wrContextArg, int32_t operation, Persistent hPromiseArg, Persistent hReasonArg): + wrContext(wrContextArg), + Operation(operation), + hPromise(hPromiseArg), + hReason(hReasonArg) + { + } + + ~PromiseRejectionEntry() + { + hPromise.Dispose(); + hReason.Dispose(); + } + }; + + using PromiseRejectionQueue = std::queue>; + struct ScriptCacheEntry final { V8DocumentInfo DocumentInfo; @@ -658,6 +684,10 @@ class V8IsolateImpl final: public V8Isolate, public v8_inspector::V8InspectorCli void OnBeforeCallEntered(); static void PromiseHook(v8::PromiseHookType type, v8::Local hPromise, v8::Local hParent); + static void PromiseRejectCallback(v8::PromiseRejectMessage message); + + void QueuePromiseRejection(V8ContextImpl& contextImpl, int32_t operation, v8::Local hPromise, v8::Local hReason); + void ProcessPromiseRejectionQueue(); void FlushContextAsync(v8::Local hContext); void FlushContextAsync(ContextEntry& contextEntry); @@ -675,8 +705,10 @@ class V8IsolateImpl final: public V8Isolate, public v8_inspector::V8InspectorCli std::shared_ptr m_spForegroundTaskRunner; std::vector> m_AsyncTasks; CallWithLockQueue m_CallWithLockQueue; + PromiseRejectionQueue m_PromiseRejectionQueue; std::condition_variable m_CallWithLockQueueChanged; size_t m_CallWithLockLevel; + bool m_PromiseRejectionNotificationPending; std::vector> m_TaskTimers; std::list m_ScriptCache; bool m_DebuggingEnabled; diff --git a/ClearScriptV8/V8SplitProxyManaged.h b/ClearScriptV8/V8SplitProxyManaged.h index 85f2b111..c2412893 100644 --- a/ClearScriptV8/V8SplitProxyManaged.h +++ b/ClearScriptV8/V8SplitProxyManaged.h @@ -77,6 +77,7 @@ V8_SPLIT_PROXY_MANAGED_METHOD(void, AsyncDisposeFastHostObject, void* pvObject, V8Value::FastResult& result) \ \ V8_SPLIT_PROXY_MANAGED_METHOD(void, QueueNativeCallback, NativeCallbackHandle* pCallbackHandle) \ + V8_SPLIT_PROXY_MANAGED_METHOD(void, NotifyPromiseRejection, void* pvCallback, int32_t operation, const V8Value& promise, const V8Value& reason) \ \ V8_SPLIT_PROXY_MANAGED_METHOD(void*, CreateNativeCallbackTimer, int32_t dueTime, int32_t period, NativeCallbackHandle* pCallbackHandle) \ V8_SPLIT_PROXY_MANAGED_METHOD(StdBool, ChangeNativeCallbackTimer, void* pvTimer, int32_t dueTime, int32_t period) \ diff --git a/ClearScriptV8/V8SplitProxyNative.cpp b/ClearScriptV8/V8SplitProxyNative.cpp index 0297cbd7..c939fa11 100644 --- a/ClearScriptV8/V8SplitProxyNative.cpp +++ b/ClearScriptV8/V8SplitProxyNative.cpp @@ -770,7 +770,7 @@ NATIVE_ENTRY_POINT(V8IsolateHandle*) V8Isolate_Create(const StdString& name, int //----------------------------------------------------------------------------- -NATIVE_ENTRY_POINT(V8ContextHandle*) V8Isolate_CreateContext(const V8IsolateHandle& handle, const StdString& name, V8Context::Flags flags, int32_t debugPort) noexcept +NATIVE_ENTRY_POINT(V8ContextHandle*) V8Isolate_CreateContext(const V8IsolateHandle& handle, const StdString& name, V8Context::Flags flags, int32_t debugPort, void* pvPromiseRejectionCallback) noexcept { auto spIsolate = handle.GetEntity(); if (!spIsolate.IsEmpty()) @@ -778,13 +778,21 @@ NATIVE_ENTRY_POINT(V8ContextHandle*) V8Isolate_CreateContext(const V8IsolateHand V8Context::Options options; options.Flags = flags; options.DebugPort = debugPort; + options.pvPromiseRejectionCallback = (pvPromiseRejectionCallback != nullptr) ? HostObjectUtil::AddRef(pvPromiseRejectionCallback) : nullptr; try { - return new V8ContextHandle(V8Context::Create(spIsolate, name, options)); + auto pContext = V8Context::Create(spIsolate, name, options); + options.pvPromiseRejectionCallback = nullptr; + return new V8ContextHandle(pContext); } catch (const V8Exception& exception) { + if (options.pvPromiseRejectionCallback != nullptr) + { + HostObjectUtil::Release(options.pvPromiseRejectionCallback); + } + exception.ScheduleScriptEngineException(); } } diff --git a/ClearScriptV8/V8SplitProxyNative.h b/ClearScriptV8/V8SplitProxyNative.h index 8ad6149e..32a11418 100644 --- a/ClearScriptV8/V8SplitProxyNative.h +++ b/ClearScriptV8/V8SplitProxyNative.h @@ -244,7 +244,7 @@ NATIVE_ENTRY_POINT(StdBool) V8CpuProfileNode_GetHitLines(const v8::CpuProfileNod NATIVE_ENTRY_POINT(const v8::CpuProfileNode*) V8CpuProfileNode_GetChildNode(const v8::CpuProfileNode& node, int32_t index) noexcept; NATIVE_ENTRY_POINT(V8IsolateHandle*) V8Isolate_Create(const StdString& name, int32_t maxNewSpaceSize, int32_t maxOldSpaceSize, double heapExpansionMultiplier, uint64_t maxArrayBufferAllocation, V8Isolate::Flags flags, int32_t debugPort) noexcept; -NATIVE_ENTRY_POINT(V8ContextHandle*) V8Isolate_CreateContext(const V8IsolateHandle& handle, const StdString& name, V8Context::Flags flags, int32_t debugPort) noexcept; +NATIVE_ENTRY_POINT(V8ContextHandle*) V8Isolate_CreateContext(const V8IsolateHandle& handle, const StdString& name, V8Context::Flags flags, int32_t debugPort, void* pvPromiseRejectionCallback) noexcept; NATIVE_ENTRY_POINT(size_t) V8Isolate_GetMaxHeapSize(const V8IsolateHandle& handle) noexcept; NATIVE_ENTRY_POINT(void) V8Isolate_SetMaxHeapSize(const V8IsolateHandle& handle, size_t size) noexcept; NATIVE_ENTRY_POINT(double) V8Isolate_GetHeapSizeSampleInterval(const V8IsolateHandle& handle) noexcept; diff --git a/NetCore/ClearScript.V8/ClearScript.V8.csproj b/NetCore/ClearScript.V8/ClearScript.V8.csproj index f4a65611..60f95d21 100644 --- a/NetCore/ClearScript.V8/ClearScript.V8.csproj +++ b/NetCore/ClearScript.V8/ClearScript.V8.csproj @@ -99,6 +99,7 @@ + diff --git a/NetFramework/ClearScript.V8/ClearScript.V8.csproj b/NetFramework/ClearScript.V8/ClearScript.V8.csproj index 5a2fbbe1..6667c9d6 100644 --- a/NetFramework/ClearScript.V8/ClearScript.V8.csproj +++ b/NetFramework/ClearScript.V8/ClearScript.V8.csproj @@ -105,6 +105,7 @@ + diff --git a/NetStandard/ClearScript.V8/ClearScript.V8.csproj b/NetStandard/ClearScript.V8/ClearScript.V8.csproj index 4df42612..76255047 100644 --- a/NetStandard/ClearScript.V8/ClearScript.V8.csproj +++ b/NetStandard/ClearScript.V8/ClearScript.V8.csproj @@ -97,6 +97,7 @@ + diff --git a/UWP/ClearScript.V8/ClearScript.V8.csproj b/UWP/ClearScript.V8/ClearScript.V8.csproj index 4e9ca770..5358c0be 100644 --- a/UWP/ClearScript.V8/ClearScript.V8.csproj +++ b/UWP/ClearScript.V8/ClearScript.V8.csproj @@ -121,6 +121,7 @@ +