Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Components/Components/src/ComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.JSInterop;

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Source-Build (Managed))

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux x64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl x64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr (Tests: macOS)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr (Tests: Ubuntu x64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: macOS arm64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Test: macOS)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: macOS x64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

Check failure on line 5 in src/Components/Components/src/ComponentBase.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Test: Ubuntu x64)

src/Components/Components/src/ComponentBase.cs#L5

src/Components/Components/src/ComponentBase.cs(5,17): error CS0234: (NETCORE_ENGINEERING_TELEMETRY=Build) The type or namespace name 'JSInterop' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)

namespace Microsoft.AspNetCore.Components;

Expand Down Expand Up @@ -289,6 +290,11 @@
{
await task;
}
catch (JSTimeoutException)
{
// Let JSTimeoutException bubble up to provide meaningful error information
throw;
}
catch // avoiding exception filters for AOT runtime support
{
// Ignore exceptions from task cancellations.
Expand Down Expand Up @@ -332,6 +338,11 @@
{
await task;
}
catch (JSTimeoutException)
{
// Let JSTimeoutException bubble up to provide meaningful error information
throw;
}
catch // avoiding exception filters for AOT runtime support
{
// Ignore exceptions from task cancellations, but don't bother issuing a state change.
Expand Down
19 changes: 19 additions & 0 deletions src/Components/Components/test/ComponentBaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Test;

Expand Down Expand Up @@ -463,6 +464,24 @@ public async Task RenderRootComponentAsync_ReportsErrorDuringOnParameterSetAsync
Assert.Same(expected, actual);
}

[Fact]
public async Task ComponentBase_AllowsJSTimeoutExceptionToBubbleUp()
{
// Arrange
var renderer = new TestRenderer();
var component = new TestComponent();

var timeoutException = new JSTimeoutException("Test timeout");
component.OnParametersSetAsyncLogic = _ => Task.FromException(timeoutException);

// Act & Assert
var componentId = renderer.AssignRootComponentId(component);
var actual = await Assert.ThrowsAsync<JSTimeoutException>(() => renderer.RenderRootComponentAsync(componentId));

// Assert
Assert.Same(timeoutException, actual);
}

private class TestComponent : ComponentBase
{
public bool RunsBaseOnInit { get; set; } = true;
Expand Down
13 changes: 11 additions & 2 deletions src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,17 @@ public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, Cancellat
if (DefaultAsyncTimeout.HasValue)
{
using var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value);
// We need to await here due to the using
return await InvokeAsync<TValue>(targetInstanceId, identifier, callType, cts.Token, args);

try
{
// We need to await here due to the using
return await InvokeAsync<TValue>(targetInstanceId, identifier, callType, cts.Token, args);
}
catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
{
// This was cancelled due to our timeout, throw a more meaningful exception
throw new JSTimeoutException("A JavaScript interop call timed out. Consider increasing the timeout duration if the operation is expected to take longer.");
}
Copy link
Member

@akoeplinger akoeplinger Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do the exception check in ComponentBase.cs like Safia mentioned in #21384 (comment)

The current code won't work if you e.g. pass in your own CancellationToken instead of setting DefaultAsyncTimeout.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be already a bigger change, with adding CancellationTokenSource parameter to public async methods like OnInitializedAsync. I did not see that when I was assigning copilot. In this case, that's not a fast fix. Let's keep it in the backlog.

}

return await InvokeAsync<TValue>(targetInstanceId, identifier, callType, CancellationToken.None, args);
Expand Down
27 changes: 27 additions & 0 deletions src/JSInterop/Microsoft.JSInterop/src/JSTimeoutException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.JSInterop;

/// <summary>
/// Represents errors that occur when a JavaScript interop call times out.
/// </summary>
public class JSTimeoutException : JSException
{
/// <summary>
/// Constructs an instance of <see cref="JSTimeoutException"/>.
/// </summary>
/// <param name="message">The exception message.</param>
public JSTimeoutException(string message) : base(message)
{
}

/// <summary>
/// Constructs an instance of <see cref="JSTimeoutException"/>.
/// </summary>
/// <param name="message">The exception message.</param>
/// <param name="innerException">The inner exception.</param>
public JSTimeoutException(string message, Exception innerException) : base(message, innerException)
{
}
}
3 changes: 3 additions & 0 deletions src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, object?[]? args
Microsoft.JSInterop.JSRuntime.InvokeNewAsync(string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask<Microsoft.JSInterop.IJSObjectReference!>
Microsoft.JSInterop.JSRuntime.SetValueAsync<TValue>(string! identifier, TValue value) -> System.Threading.Tasks.ValueTask
Microsoft.JSInterop.JSRuntime.SetValueAsync<TValue>(string! identifier, TValue value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
Microsoft.JSInterop.JSTimeoutException
Microsoft.JSInterop.JSTimeoutException.JSTimeoutException(string! message) -> void
Microsoft.JSInterop.JSTimeoutException.JSTimeoutException(string! message, System.Exception! innerException) -> void
static Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSObjectReference! jsObjectReference, string! identifier, params object?[]? args) -> System.Threading.Tasks.ValueTask<Microsoft.JSInterop.IJSObjectReference!>
static Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSObjectReference! jsObjectReference, string! identifier, System.Threading.CancellationToken cancellationToken, object?[]? args) -> System.Threading.Tasks.ValueTask<Microsoft.JSInterop.IJSObjectReference!>
static Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeNewAsync(this Microsoft.JSInterop.IJSObjectReference! jsObjectReference, string! identifier, System.TimeSpan timeout, object?[]? args) -> System.Threading.Tasks.ValueTask<Microsoft.JSInterop.IJSObjectReference!>
Expand Down
4 changes: 2 additions & 2 deletions src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void DispatchesAsyncCallsWithDistinctAsyncHandles()
}

[Fact]
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
public async Task InvokeAsync_ThrowsJSTimeoutException_AfterDefaultTimeout()
{
// Arrange
var runtime = new TestJSRuntime();
Expand All @@ -47,7 +47,7 @@ public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);

// Assert
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
await Assert.ThrowsAsync<JSTimeoutException>(async () => await task);
}

[Fact]
Expand Down
Loading