From 07add441f014bf5904f99678782e71ba4f0417a2 Mon Sep 17 00:00:00 2001 From: Anna Sas Date: Sun, 3 May 2026 12:56:48 +0200 Subject: [PATCH] Refactor: Improve `InfiniFrameWebViewManager` message handling and add unit tests for `SendMessage` after disposal --- .../InfiniFrameWebViewManager.cs | 30 ++++----- .../InfiniFrameWebViewManagerTests.cs | 62 +++++++++++++++++++ 2 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 tests/InfiniFrameTests.BlazorWebView/InfiniFrameWebViewManagerTests.cs diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs index 0245e396..0fe168da 100644 --- a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs +++ b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs @@ -229,9 +229,8 @@ protected override void NavigateCore(Uri absoluteUri) { } protected override void SendMessage(string message) { - while (!_channel.Writer.TryWrite(message)) { - Thread.Sleep(200); - } + if (_channel.Writer.TryWrite(message)) return; + LazyLogger.Value?.LogDebug("Skipping WebView message because the message channel is closed."); } private async Task MessagePump() { @@ -253,20 +252,23 @@ private async Task MessagePump() { } protected override async ValueTask DisposeAsyncCore() { - try { _channel.Writer.Complete(); } - catch (ChannelClosedException ex) { - LazyLogger.Value?.LogDebug(ex, "Channel was already closed during dispose."); - } - try { - await _messagePumpTask.WaitAsync(TimeSpan.FromSeconds(5)); - } - catch (TimeoutException) { - LazyLogger.Value?.LogWarning( - "Timed out while waiting for WebView message pump shutdown."); + await base.DisposeAsyncCore(); } + finally { + try { _channel.Writer.Complete(); } + catch (ChannelClosedException ex) { + LazyLogger.Value?.LogDebug(ex, "Channel was already closed during dispose."); + } - await base.DisposeAsyncCore(); + try { + await _messagePumpTask.WaitAsync(TimeSpan.FromSeconds(5)); + } + catch (TimeoutException) { + LazyLogger.Value?.LogWarning( + "Timed out while waiting for WebView message pump shutdown."); + } + } } private static bool IsNonFatalException(Exception exception) diff --git a/tests/InfiniFrameTests.BlazorWebView/InfiniFrameWebViewManagerTests.cs b/tests/InfiniFrameTests.BlazorWebView/InfiniFrameWebViewManagerTests.cs new file mode 100644 index 00000000..d0a496a0 --- /dev/null +++ b/tests/InfiniFrameTests.BlazorWebView/InfiniFrameWebViewManagerTests.cs @@ -0,0 +1,62 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Imports +// --------------------------------------------------------------------------------------------------------------------- +using InfiniFrame; +using InfiniFrame.BlazorWebView; +using InfiniFrameTests.Shared; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using NSubstitute; + +namespace InfiniFrameTests.BlazorWebView; + +// --------------------------------------------------------------------------------------------------------------------- +// Code +// --------------------------------------------------------------------------------------------------------------------- +public class InfiniFrameWebViewManagerTests { + private sealed class TestableInfiniFrameWebViewManager( + IInfiniFrameWindowBuilder builder, + IServiceProvider provider, + Dispatcher dispatcher, + IFileProvider fileProvider, + JSComponentConfigurationStore jsComponents, + IOptions config + ) : InfiniFrameWebViewManager(builder, provider, dispatcher, fileProvider, jsComponents, config) { + public void SendMessageForTest(string message) => SendMessage(message); + } + + [Test] + [Timeout(TimeoutUtility.DefaultTimeout)] + public async Task SendMessage_AfterDispose_ShouldReturnPromptly(CancellationToken ct) { + // Arrange + IInfiniFrameWindow window = Substitute.For(); + window.SendWebMessageAsync(Arg.Any(), Arg.Any()) + .Returns(Task.CompletedTask); + + await using ServiceProvider provider = new ServiceCollection() + .AddLogging() + .AddSingleton(window) + .BuildServiceProvider(); + + var dispatcher = Substitute.For(); + var manager = new TestableInfiniFrameWebViewManager( + InfiniFrameWindowBuilder.Create(), + provider, + dispatcher, + new NullFileProvider(), + new JSComponentConfigurationStore(), + Options.Create(new InfiniFrameBlazorAppConfiguration())); + + await manager.DisposeAsync(); + + // Act + Task sendTask = Task.Run(() => manager.SendMessageForTest("late-dispose-message"), ct); + + // Assert + await sendTask.WaitAsync(TimeSpan.FromSeconds(1), ct); + await window.DidNotReceive().SendWebMessageAsync("late-dispose-message", Arg.Any()); + } +}