Fix some issues with Ignitor (#15276)
* Avoid null ref when FormatException is not set * Handle errors when the client is canceled but an operation is in progress
This commit is contained in:
parent
8c77190db5
commit
9f9fbd0290
|
|
@ -13,17 +13,21 @@ using Microsoft.AspNetCore.SignalR.Protocol;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public class BlazorClient : IAsyncDisposable
|
public class BlazorClient : IAsyncDisposable
|
||||||
{
|
{
|
||||||
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
|
private const string MarkerPattern = ".*?<!--Blazor:(.*?)-->.*?";
|
||||||
|
private HubConnection? _hubConnection;
|
||||||
|
|
||||||
public BlazorClient()
|
public BlazorClient()
|
||||||
{
|
{
|
||||||
CancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource = new CancellationTokenSource();
|
||||||
TaskCompletionSource = new TaskCompletionSource<object>();
|
CancellationToken = CancellationTokenSource.Token;
|
||||||
|
TaskCompletionSource = new TaskCompletionSource<object?>();
|
||||||
|
|
||||||
CancellationTokenSource.Token.Register(() =>
|
CancellationTokenSource.Token.Register(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -44,104 +48,103 @@ namespace Ignitor
|
||||||
/// Gets the collections of operation results that are captured when <see cref="CaptureOperations"/>
|
/// Gets the collections of operation results that are captured when <see cref="CaptureOperations"/>
|
||||||
/// is true.
|
/// is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Operations Operations { get; private set; }
|
public Operations Operations { get; } = new Operations();
|
||||||
|
|
||||||
public Func<string, Exception> FormatError { get; set; }
|
public Func<string, Exception>? FormatError { get; set; }
|
||||||
|
|
||||||
private CancellationTokenSource CancellationTokenSource { get; }
|
private CancellationTokenSource CancellationTokenSource { get; }
|
||||||
|
|
||||||
private CancellationToken CancellationToken => CancellationTokenSource.Token;
|
private CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
private TaskCompletionSource<object> TaskCompletionSource { get; }
|
private TaskCompletionSource<object?> TaskCompletionSource { get; }
|
||||||
|
|
||||||
private CancellableOperation<CapturedAttachComponentCall> NextAttachComponentReceived { get; set; }
|
private CancellableOperation<CapturedAttachComponentCall>? NextAttachComponentReceived { get; set; }
|
||||||
|
|
||||||
private CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
|
private CancellableOperation<CapturedRenderBatch?>? NextBatchReceived { get; set; }
|
||||||
|
|
||||||
private CancellableOperation<string> NextErrorReceived { get; set; }
|
private CancellableOperation<string?>? NextErrorReceived { get; set; }
|
||||||
|
|
||||||
private CancellableOperation<Exception> NextDisconnect { get; set; }
|
private CancellableOperation<Exception?>? NextDisconnect { get; set; }
|
||||||
|
|
||||||
private CancellableOperation<CapturedJSInteropCall> NextJSInteropReceived { get; set; }
|
private CancellableOperation<CapturedJSInteropCall?>? NextJSInteropReceived { get; set; }
|
||||||
|
|
||||||
private CancellableOperation<string> NextDotNetInteropCompletionReceived { get; set; }
|
private CancellableOperation<string?>? NextDotNetInteropCompletionReceived { get; set; }
|
||||||
|
|
||||||
public ILoggerProvider LoggerProvider { get; set; }
|
public ILoggerProvider LoggerProvider { get; set; } = NullLoggerProvider.Instance;
|
||||||
|
|
||||||
public bool ConfirmRenderBatch { get; set; } = true;
|
public bool ConfirmRenderBatch { get; set; } = true;
|
||||||
|
|
||||||
public event Action<CapturedJSInteropCall> JSInterop;
|
public event Action<CapturedJSInteropCall>? JSInterop;
|
||||||
|
|
||||||
public event Action<CapturedRenderBatch> RenderBatchReceived;
|
public event Action<CapturedRenderBatch>? RenderBatchReceived;
|
||||||
|
|
||||||
public event Action<string> DotNetInteropCompletion;
|
public event Action<string>? DotNetInteropCompletion;
|
||||||
|
|
||||||
public event Action<string> OnCircuitError;
|
public event Action<string>? OnCircuitError;
|
||||||
|
|
||||||
public string CircuitId { get; set; }
|
public string? CircuitId { get; private set; }
|
||||||
|
|
||||||
public ElementHive Hive { get; set; } = new ElementHive();
|
public ElementHive Hive { get; } = new ElementHive();
|
||||||
|
|
||||||
public bool ImplicitWait => DefaultOperationTimeout != null;
|
public bool ImplicitWait => DefaultOperationTimeout != null;
|
||||||
|
|
||||||
public HubConnection HubConnection { get; private set; }
|
public HubConnection HubConnection => _hubConnection ?? throw new InvalidOperationException("HubConnection has not been initialized.");
|
||||||
|
|
||||||
public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
|
public Task<CapturedRenderBatch?> PrepareForNextBatch(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextBatchReceived != null && !NextBatchReceived.Disposed)
|
if (NextBatchReceived != null && !NextBatchReceived.Disposed)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextBatchReceived = new CancellableOperation<CapturedRenderBatch>(timeout);
|
NextBatchReceived = new CancellableOperation<CapturedRenderBatch?>(timeout, CancellationToken);
|
||||||
|
|
||||||
return NextBatchReceived.Completion.Task;
|
return NextBatchReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
|
public Task<CapturedJSInteropCall?> PrepareForNextJSInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed)
|
if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall>(timeout);
|
NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall?>(timeout, CancellationToken);
|
||||||
|
|
||||||
return NextJSInteropReceived.Completion.Task;
|
return NextJSInteropReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
|
public Task<string?> PrepareForNextDotNetInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed)
|
if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextDotNetInteropCompletionReceived = new CancellableOperation<string>(timeout);
|
NextDotNetInteropCompletionReceived = new CancellableOperation<string?>(timeout, CancellationToken);
|
||||||
|
|
||||||
return NextDotNetInteropCompletionReceived.Completion.Task;
|
return NextDotNetInteropCompletionReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
|
public Task<string?> PrepareForNextCircuitError(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextErrorReceived != null && !NextErrorReceived.Disposed)
|
if (NextErrorReceived != null && !NextErrorReceived.Disposed)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextErrorReceived = new CancellableOperation<string>(timeout);
|
NextErrorReceived = new CancellableOperation<string?>(timeout, CancellationToken);
|
||||||
|
|
||||||
return NextErrorReceived.Completion.Task;
|
return NextErrorReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout)
|
public Task<Exception?> PrepareForNextDisconnect(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextDisconnect != null && !NextDisconnect.Disposed)
|
if (NextDisconnect != null && !NextDisconnect.Disposed)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextDisconnect = new CancellableOperation<Exception>(timeout);
|
NextDisconnect = new CancellableOperation<Exception?>(timeout, CancellationToken);
|
||||||
|
|
||||||
return NextDisconnect.Completion.Task;
|
return NextDisconnect.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
@ -172,44 +175,44 @@ namespace Ignitor
|
||||||
return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
|
return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CapturedRenderBatch> ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<CapturedRenderBatch?> ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForRenderBatch(timeout);
|
var task = WaitForRenderBatch(timeout);
|
||||||
await action();
|
await action();
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CapturedJSInteropCall> ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<CapturedJSInteropCall?> ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForJSInterop(timeout);
|
var task = WaitForJSInterop(timeout);
|
||||||
await action();
|
await action();
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<string?> ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForDotNetInterop(timeout);
|
var task = WaitForDotNetInterop(timeout);
|
||||||
await action();
|
await action();
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<string?> ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForCircuitError(timeout);
|
var task = WaitForCircuitError(timeout);
|
||||||
await action();
|
await action();
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Exception> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<Exception?> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForDisconnect(timeout);
|
var task = WaitForDisconnect(timeout);
|
||||||
await action();
|
await action();
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(string error, Exception exception)> ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<(string? error, Exception? exception)> ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
string error = null;
|
string? error = default;
|
||||||
|
|
||||||
// NOTE: timeout is used for each operation individually.
|
// NOTE: timeout is used for each operation individually.
|
||||||
var exception = await ExpectDisconnect(async () =>
|
var exception = await ExpectDisconnect(async () =>
|
||||||
|
|
@ -220,7 +223,7 @@ namespace Ignitor
|
||||||
return (error, exception);
|
return (error, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CapturedRenderBatch> WaitForRenderBatch(TimeSpan? timeout = null)
|
private async Task<CapturedRenderBatch?> WaitForRenderBatch(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
|
|
@ -233,7 +236,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
return await PrepareForNextBatch(timeout ?? DefaultOperationTimeout);
|
return await PrepareForNextBatch(timeout ?? DefaultOperationTimeout);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (TimeoutException) when (FormatError != null)
|
||||||
{
|
{
|
||||||
throw FormatError("Timed out while waiting for batch.");
|
throw FormatError("Timed out while waiting for batch.");
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +245,7 @@ namespace Ignitor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CapturedJSInteropCall> WaitForJSInterop(TimeSpan? timeout = null)
|
private async Task<CapturedJSInteropCall?> WaitForJSInterop(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
|
|
@ -255,7 +258,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
return await PrepareForNextJSInterop(timeout ?? DefaultOperationTimeout);
|
return await PrepareForNextJSInterop(timeout ?? DefaultOperationTimeout);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (TimeoutException) when (FormatError != null)
|
||||||
{
|
{
|
||||||
throw FormatError("Timed out while waiting for JS Interop.");
|
throw FormatError("Timed out while waiting for JS Interop.");
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +267,7 @@ namespace Ignitor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> WaitForDotNetInterop(TimeSpan? timeout = null)
|
private async Task<string?> WaitForDotNetInterop(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
|
|
@ -277,7 +280,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
return await PrepareForNextDotNetInterop(timeout ?? DefaultOperationTimeout);
|
return await PrepareForNextDotNetInterop(timeout ?? DefaultOperationTimeout);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (TimeoutException) when (FormatError != null)
|
||||||
{
|
{
|
||||||
throw FormatError("Timed out while waiting for .NET interop.");
|
throw FormatError("Timed out while waiting for .NET interop.");
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +289,7 @@ namespace Ignitor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> WaitForCircuitError(TimeSpan? timeout = null)
|
private async Task<string?> WaitForCircuitError(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
|
|
@ -299,7 +302,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout);
|
return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (TimeoutException) when (FormatError != null)
|
||||||
{
|
{
|
||||||
throw FormatError("Timed out while waiting for circuit error.");
|
throw FormatError("Timed out while waiting for circuit error.");
|
||||||
}
|
}
|
||||||
|
|
@ -308,7 +311,7 @@ namespace Ignitor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Exception> WaitForDisconnect(TimeSpan? timeout = null)
|
private async Task<Exception?> WaitForDisconnect(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
|
|
@ -321,7 +324,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
return await PrepareForNextDisconnect(timeout ?? DefaultOperationTimeout);
|
return await PrepareForNextDisconnect(timeout ?? DefaultOperationTimeout);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (TimeoutException) when (FormatError != null)
|
||||||
{
|
{
|
||||||
throw FormatError("Timed out while waiting for disconnect.");
|
throw FormatError("Timed out while waiting for disconnect.");
|
||||||
}
|
}
|
||||||
|
|
@ -344,7 +347,7 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
HubConnection = builder.Build();
|
_hubConnection = builder.Build();
|
||||||
await HubConnection.StartAsync(CancellationToken);
|
await HubConnection.StartAsync(CancellationToken);
|
||||||
|
|
||||||
HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent);
|
HubConnection.On<int, string>("JS.AttachComponent", OnAttachComponent);
|
||||||
|
|
@ -354,11 +357,6 @@ namespace Ignitor
|
||||||
HubConnection.On<string>("JS.Error", OnError);
|
HubConnection.On<string>("JS.Error", OnError);
|
||||||
HubConnection.Closed += OnClosedAsync;
|
HubConnection.Closed += OnClosedAsync;
|
||||||
|
|
||||||
if (CaptureOperations)
|
|
||||||
{
|
|
||||||
Operations = new Operations();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connectAutomatically)
|
if (!connectAutomatically)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -366,7 +364,7 @@ namespace Ignitor
|
||||||
|
|
||||||
var descriptors = await GetPrerenderDescriptors(uri);
|
var descriptors = await GetPrerenderDescriptors(uri);
|
||||||
await ExpectRenderBatch(
|
await ExpectRenderBatch(
|
||||||
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors),
|
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors, CancellationToken),
|
||||||
DefaultConnectionTimeout);
|
DefaultConnectionTimeout);
|
||||||
return CircuitId != null;
|
return CircuitId != null;
|
||||||
}
|
}
|
||||||
|
|
@ -415,9 +413,9 @@ namespace Ignitor
|
||||||
NextBatchReceived?.Completion?.TrySetResult(null);
|
NextBatchReceived?.Completion?.TrySetResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ConfirmBatch(int batchId, string error = null)
|
public Task ConfirmBatch(int batchId, string? error = null)
|
||||||
{
|
{
|
||||||
return HubConnection.InvokeAsync("OnRenderCompleted", batchId, error);
|
return HubConnection.InvokeAsync("OnRenderCompleted", batchId, error, CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnError(string error)
|
private void OnError(string error)
|
||||||
|
|
@ -468,7 +466,14 @@ namespace Ignitor
|
||||||
|
|
||||||
public async Task InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson)
|
public async Task InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
await ExpectDotNetInterop(() => HubConnection.InvokeAsync("BeginInvokeDotNetFromJS", callId?.ToString(), assemblyName, methodIdentifier, dotNetObjectId ?? 0, argsJson));
|
await ExpectDotNetInterop(() => HubConnection.InvokeAsync(
|
||||||
|
"BeginInvokeDotNetFromJS",
|
||||||
|
callId?.ToString(),
|
||||||
|
assemblyName,
|
||||||
|
methodIdentifier,
|
||||||
|
dotNetObjectId ?? 0,
|
||||||
|
argsJson,
|
||||||
|
CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetPrerenderDescriptors(Uri uri)
|
public async Task<string> GetPrerenderDescriptors(Uri uri)
|
||||||
|
|
@ -485,8 +490,11 @@ namespace Ignitor
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
CancellationTokenSource.Cancel();
|
if (!CancellationTokenSource.IsCancellationRequested)
|
||||||
CancellationTokenSource.Dispose();
|
{
|
||||||
|
CancellationTokenSource.Cancel();
|
||||||
|
CancellationTokenSource.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ElementNode FindElementById(string id)
|
public ElementNode FindElementById(string id)
|
||||||
|
|
@ -519,6 +527,7 @@ namespace Ignitor
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
|
Cancel();
|
||||||
if (HubConnection != null)
|
if (HubConnection != null)
|
||||||
{
|
{
|
||||||
await HubConnection.DisposeAsync();
|
await HubConnection.DisposeAsync();
|
||||||
|
|
@ -526,3 +535,5 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
internal class CancellableOperation<TResult>
|
internal class CancellableOperation<TResult>
|
||||||
{
|
{
|
||||||
public CancellableOperation(TimeSpan? timeout)
|
public CancellableOperation(TimeSpan? timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Timeout = timeout;
|
Timeout = timeout;
|
||||||
|
|
||||||
|
|
@ -17,25 +18,37 @@ namespace Ignitor
|
||||||
Completion.Task.ContinueWith(
|
Completion.Task.ContinueWith(
|
||||||
(task, state) =>
|
(task, state) =>
|
||||||
{
|
{
|
||||||
var operation = (CancellableOperation<TResult>)state;
|
var operation = (CancellableOperation<TResult>)state!;
|
||||||
operation.Dispose();
|
operation.Dispose();
|
||||||
},
|
},
|
||||||
this,
|
this,
|
||||||
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
|
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
|
||||||
|
|
||||||
|
|
||||||
|
Cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
|
||||||
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
|
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
|
||||||
{
|
{
|
||||||
Cancellation = new CancellationTokenSource(Timeout.Value);
|
Cancellation.CancelAfter(Timeout.Value);
|
||||||
CancellationRegistration = Cancellation.Token.Register(
|
|
||||||
(self) =>
|
|
||||||
{
|
|
||||||
var operation = (CancellableOperation<TResult>)self;
|
|
||||||
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
|
|
||||||
operation.Cancellation.Dispose();
|
|
||||||
operation.CancellationRegistration.Dispose();
|
|
||||||
},
|
|
||||||
this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancellationRegistration = Cancellation.Token.Register(
|
||||||
|
(self) =>
|
||||||
|
{
|
||||||
|
var operation = (CancellableOperation<TResult>)self!;
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// The operation was externally canceled before it timed out.
|
||||||
|
Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.Completion.TrySetException(new TimeoutException($"The operation timed out after {Timeout}."));
|
||||||
|
operation.Cancellation?.Dispose();
|
||||||
|
operation.CancellationRegistration.Dispose();
|
||||||
|
},
|
||||||
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan? Timeout { get; }
|
public TimeSpan? Timeout { get; }
|
||||||
|
|
@ -62,3 +75,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
|
#nullable enable
|
||||||
public class ComponentState
|
public class ComponentState
|
||||||
{
|
{
|
||||||
public ComponentState(int componentId)
|
public ComponentState(int componentId)
|
||||||
|
|
@ -11,6 +12,7 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ComponentId { get; }
|
public int ComponentId { get; }
|
||||||
public IComponent Component { get; }
|
public IComponent? Component { get; }
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public abstract class ContainerNode : Node
|
public abstract class ContainerNode : Node
|
||||||
|
|
@ -82,3 +83,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public class ElementHive
|
public class ElementHive
|
||||||
|
|
@ -35,7 +37,7 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryFindElementById(string id, out ElementNode element)
|
public bool TryFindElementById(string id, [NotNullWhen(true)] out ElementNode? element)
|
||||||
{
|
{
|
||||||
foreach (var kvp in Components)
|
foreach (var kvp in Components)
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +51,7 @@ namespace Ignitor
|
||||||
element = null;
|
element = null;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool TryGetElementFromChildren(Node node, out ElementNode foundNode)
|
bool TryGetElementFromChildren(Node node, out ElementNode? foundNode)
|
||||||
{
|
{
|
||||||
if (node is ElementNode elementNode &&
|
if (node is ElementNode elementNode &&
|
||||||
elementNode.Attributes.TryGetValue("id", out var elementId) &&
|
elementNode.Attributes.TryGetValue("id", out var elementId) &&
|
||||||
|
|
@ -81,6 +83,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
component = new ComponentNode(componentId);
|
component = new ComponentNode(componentId);
|
||||||
Components.Add(componentId, component);
|
Components.Add(componentId, component);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyEdits(batch, component, 0, edits);
|
ApplyEdits(batch, component, 0, edits);
|
||||||
|
|
@ -199,7 +202,7 @@ namespace Ignitor
|
||||||
|
|
||||||
case RenderTreeEditType.StepOut:
|
case RenderTreeEditType.StepOut:
|
||||||
{
|
{
|
||||||
parent = parent.Parent;
|
parent = parent.Parent ?? throw new InvalidOperationException($"Cannot step out of {parent}");
|
||||||
currentDepth--;
|
currentDepth--;
|
||||||
childIndexAtCurrentDepth = currentDepth == 0 ? childIndex : 0; // The childIndex is only ever nonzero at zero depth
|
childIndexAtCurrentDepth = currentDepth == 0 ? childIndex : 0; // The childIndex is only ever nonzero at zero depth
|
||||||
break;
|
break;
|
||||||
|
|
@ -469,3 +472,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public class ElementNode : ContainerNode
|
public class ElementNode : ContainerNode
|
||||||
|
|
@ -87,7 +88,7 @@ namespace Ignitor
|
||||||
return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(args));
|
return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ClickAsync(HubConnection connection)
|
internal Task ClickAsync(HubConnection connection)
|
||||||
{
|
{
|
||||||
if (!Events.TryGetValue("click", out var clickEventDescriptor))
|
if (!Events.TryGetValue("click", out var clickEventDescriptor))
|
||||||
{
|
{
|
||||||
|
|
@ -129,3 +130,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public class Error
|
public class Error
|
||||||
{
|
{
|
||||||
public string Stack { get; set; }
|
public string? Stack { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
using System.Collections.Generic;
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public abstract class Node
|
public abstract class Node
|
||||||
{
|
{
|
||||||
public virtual ContainerNode Parent { get; set; }
|
public virtual ContainerNode? Parent { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
internal static class NodeSerializer
|
internal static class NodeSerializer
|
||||||
|
|
@ -96,7 +98,7 @@ namespace Ignitor
|
||||||
if (attribute.Value != null)
|
if (attribute.Value != null)
|
||||||
{
|
{
|
||||||
Write("=\"");
|
Write("=\"");
|
||||||
Write(attribute.Value.ToString());
|
Write(attribute.Value.ToString()!);
|
||||||
Write("\"");
|
Write("\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +115,7 @@ namespace Ignitor
|
||||||
if (properties.Value != null)
|
if (properties.Value != null)
|
||||||
{
|
{
|
||||||
Write("=\"");
|
Write("=\"");
|
||||||
Write(properties.Value.ToString());
|
Write(properties.Value.ToString()!);
|
||||||
Write("\"");
|
Write("\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,3 +196,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public sealed class Operations
|
public sealed class Operations
|
||||||
|
|
@ -18,3 +19,4 @@ namespace Ignitor
|
||||||
public ConcurrentQueue<CapturedJSInteropCall> JSInteropCalls { get; } = new ConcurrentQueue<CapturedJSInteropCall>();
|
public ConcurrentQueue<CapturedJSInteropCall> JSInteropCalls { get; } = new ConcurrentQueue<CapturedJSInteropCall>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
public static class RenderBatchReader
|
public static class RenderBatchReader
|
||||||
|
|
@ -206,7 +208,7 @@ namespace Ignitor
|
||||||
return new ArrayRange<ulong>(Array.Empty<ulong>(), 0);
|
return new ArrayRange<ulong>(Array.Empty<ulong>(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ReadString(ReadOnlySpan<byte> data, string[] strings)
|
private static string? ReadString(ReadOnlySpan<byte> data, string[] strings)
|
||||||
{
|
{
|
||||||
var index = BitConverter.ToInt32(data.Slice(0, 4));
|
var index = BitConverter.ToInt32(data.Slice(0, 4));
|
||||||
return index >= 0 ? strings[index] : null;
|
return index >= 0 ? strings[index] : null;
|
||||||
|
|
@ -279,3 +281,4 @@ namespace Ignitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue