Skip to content

Commit 527a213

Browse files
committed
Handle reflection exceptions
1 parent 1f92ff5 commit 527a213

File tree

2 files changed

+43
-19
lines changed

2 files changed

+43
-19
lines changed

src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests_ExceptionHandling.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,45 +33,45 @@ public Task CSharpMethodThatThrowsException_ShouldPropagateToJavaScript() =>
3333
});
3434

3535
[Fact]
36-
public Task CSharpMethodThatThrowsCustomException_ShouldIncludeExceptionDetails() =>
36+
public Task CSharpAsyncMethodThatThrowsException_ShouldPropagateToJavaScript() =>
3737
RunTest("exception-tests.html", async (hybridWebView) =>
3838
{
3939
var invokeJavaScriptTarget = new TestExceptionMethods();
4040
hybridWebView.SetInvokeJavaScriptTarget(invokeJavaScriptTarget);
4141

42-
// Tell JavaScript to invoke the method that throws a custom exception
43-
hybridWebView.SendRawMessage("ThrowCustomException");
42+
// Tell JavaScript to invoke the async method that throws an exception
43+
hybridWebView.SendRawMessage("ThrowExceptionAsync");
4444

4545
// Wait for JavaScript to handle the exception
4646
await WebViewHelpers.WaitForHtmlStatusSet(hybridWebView);
4747

48-
// Check that JavaScript caught the exception with custom details
49-
var errorType = await hybridWebView.EvaluateJavaScriptAsync("GetLastErrorType()");
50-
var errorMessage = await hybridWebView.EvaluateJavaScriptAsync("GetLastErrorMessage()");
48+
// Check that JavaScript caught the async exception
49+
var caughtError = await hybridWebView.EvaluateJavaScriptAsync("GetLastError()");
50+
Assert.Equal("Async test exception", caughtError);
5151

52-
Assert.Equal("ArgumentException", errorType);
53-
Assert.Equal("Custom argument exception", errorMessage);
52+
// Check that the method was called before throwing
53+
Assert.Equal("ThrowExceptionAsync", invokeJavaScriptTarget.LastMethodCalled);
5454
});
5555

5656
[Fact]
57-
public Task CSharpAsyncMethodThatThrowsException_ShouldPropagateToJavaScript() =>
57+
public Task CSharpMethodThatThrowsCustomException_ShouldIncludeExceptionDetails() =>
5858
RunTest("exception-tests.html", async (hybridWebView) =>
5959
{
6060
var invokeJavaScriptTarget = new TestExceptionMethods();
6161
hybridWebView.SetInvokeJavaScriptTarget(invokeJavaScriptTarget);
6262

63-
// Tell JavaScript to invoke the async method that throws an exception
64-
hybridWebView.SendRawMessage("ThrowExceptionAsync");
63+
// Tell JavaScript to invoke the method that throws a custom exception
64+
hybridWebView.SendRawMessage("ThrowCustomException");
6565

6666
// Wait for JavaScript to handle the exception
6767
await WebViewHelpers.WaitForHtmlStatusSet(hybridWebView);
6868

69-
// Check that JavaScript caught the async exception
70-
var caughtError = await hybridWebView.EvaluateJavaScriptAsync("GetLastError()");
71-
Assert.Equal("Async test exception", caughtError);
69+
// Check that JavaScript caught the exception with custom details
70+
var errorType = await hybridWebView.EvaluateJavaScriptAsync("GetLastErrorType()");
71+
var errorMessage = await hybridWebView.EvaluateJavaScriptAsync("GetLastErrorMessage()");
7272

73-
// Check that the method was called before throwing
74-
Assert.Equal("ThrowExceptionAsync", invokeJavaScriptTarget.LastMethodCalled);
73+
Assert.Equal("ArgumentException", errorType);
74+
Assert.Equal("Custom argument exception", errorMessage);
7575
});
7676

7777
[Fact]
@@ -89,11 +89,11 @@ public Task CSharpMethodThatSucceeds_ShouldStillWorkNormally() =>
8989

9090
// Check that JavaScript got the normal result
9191
var result = await hybridWebView.EvaluateJavaScriptAsync("GetLastResult()");
92-
Assert.Equal("Success!", result);
92+
Assert.Contains("Success!", result, StringComparison.Ordinal);
9393

9494
// Check that no error was thrown
9595
var hasError = await hybridWebView.EvaluateJavaScriptAsync("HasError()");
96-
Assert.Equal("false", hasError);
96+
Assert.Contains("false", hasError, StringComparison.Ordinal);
9797
});
9898

9999
private class TestExceptionMethods

src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
using System.Diagnostics.CodeAnalysis;
3434
using System.Reflection;
3535
using Microsoft.Extensions.Logging;
36+
using System.Runtime.ExceptionServices;
3637

3738
namespace Microsoft.Maui.Handlers
3839
{
@@ -278,7 +279,7 @@ private static DotNetInvokeResult CreateErrorResult(Exception ex)
278279
}
279280

280281
// invoke the .NET method
281-
var dotnetReturnValue = dotnetMethod.Invoke(jsInvokeTarget, invokeParamValues);
282+
var dotnetReturnValue = GetDotNetMethodReturnValue(jsInvokeTarget, dotnetMethod, invokeParamValues);
282283

283284
if (dotnetReturnValue is null) // null result
284285
{
@@ -303,6 +304,29 @@ private static DotNetInvokeResult CreateErrorResult(Exception ex)
303304
return dotnetReturnValue; // regular result
304305
}
305306

307+
private static object? GetDotNetMethodReturnValue(object jsInvokeTarget, MethodInfo dotnetMethod, object?[]? invokeParamValues)
308+
{
309+
try
310+
{
311+
// invoke the .NET method
312+
return dotnetMethod.Invoke(jsInvokeTarget, invokeParamValues);
313+
}
314+
catch (TargetInvocationException tie) // unwrap while preserving original stack trace
315+
{
316+
if (tie.InnerException is not null)
317+
{
318+
// Rethrow the underlying exception without losing its original stack trace
319+
ExceptionDispatchInfo.Capture(tie.InnerException).Throw();
320+
321+
// unreachable, but required for compiler flow analysis
322+
throw;
323+
}
324+
325+
// no inner exception; rethrow the TargetInvocationException itself preserving its stack
326+
throw;
327+
}
328+
}
329+
306330
private sealed class JSInvokeMethodData
307331
{
308332
public string? MethodName { get; set; }

0 commit comments

Comments
 (0)