fix(SbMarkdownEditor): improve disposal handling and error management during editor destruction

- Introduce a _disposed flag to prevent operations on disposed instances.
- Enhance error handling in DisposeAsync and editor destruction methods to log exceptions without crashing.
- Ensure proper checks before invoking interop methods to maintain stability during component lifecycle events.
This commit is contained in:
2026-06-29 14:29:17 +03:30
parent 239b1780fc
commit 6b9eb5c8fe
@@ -58,19 +58,29 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
private bool _lastIncludeDefaultToolbarItems; private bool _lastIncludeDefaultToolbarItems;
private List<SbMarkdownToolbarItem> _toolbarItems = new(); private List<SbMarkdownToolbarItem> _toolbarItems = new();
private bool _useFallback; private bool _useFallback;
private bool _disposed;
protected bool IsReady { get; private set; } protected bool IsReady { get; private set; }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (_disposed)
{
return;
}
if (firstRender) if (firstRender)
{ {
_interop = new SbMarkdownEditorInterop(JSRuntime); _interop = new SbMarkdownEditorInterop(JSRuntime);
_dotNetRef = DotNetObjectReference.Create(this);
await LoadToolbarItemsAsync(); await LoadToolbarItemsAsync();
await InitializeEditorAsync(); await InitializeEditorAsync();
if (_disposed)
{
return;
}
_lastRenderedValue = Value; _lastRenderedValue = Value;
_lastRenderedOriginal = OriginalValue; _lastRenderedOriginal = OriginalValue;
_lastRenderedSuggested = SuggestedValue; _lastRenderedSuggested = SuggestedValue;
@@ -149,11 +159,18 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
private async Task InitializeEditorAsync() private async Task InitializeEditorAsync()
{ {
if (_disposed || _interop == null)
{
return;
}
try try
{ {
_dotNetRef ??= DotNetObjectReference.Create(this);
if (IsDiffReview) if (IsDiffReview)
{ {
_editorId = await _interop!.InitDiffReviewAsync(_diffContainer, _dotNetRef!, new SbMarkdownDiffInitOptions _editorId = await _interop.InitDiffReviewAsync(_diffContainer, _dotNetRef, new SbMarkdownDiffInitOptions
{ {
EditorId = _elementId, EditorId = _elementId,
Original = OriginalValue, Original = OriginalValue,
@@ -164,14 +181,19 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
} }
else else
{ {
await _interop!.EnsureAssetsAsync(new SbMarkdownAssetOptions await _interop.EnsureAssetsAsync(new SbMarkdownAssetOptions
{ {
EnableMermaid = EnableMermaid, EnableMermaid = EnableMermaid,
EnableHighlight = EnableHighlight, EnableHighlight = EnableHighlight,
HighlightTheme = HighlightTheme HighlightTheme = HighlightTheme
}); });
_editorId = await _interop.InitEditorAsync(_textarea, _dotNetRef!, new SbMarkdownEditorInitOptions if (_disposed)
{
return;
}
_editorId = await _interop.InitEditorAsync(_textarea, _dotNetRef, new SbMarkdownEditorInitOptions
{ {
EditorId = GetEditorId(), EditorId = GetEditorId(),
Value = Value, Value = Value,
@@ -189,6 +211,11 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
}); });
} }
if (_disposed)
{
return;
}
IsReady = !string.IsNullOrEmpty(_editorId); IsReady = !string.IsNullOrEmpty(_editorId);
_useFallback = !IsReady; _useFallback = !IsReady;
} }
@@ -201,6 +228,11 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
private async Task ReinitializeEditorAsync() private async Task ReinitializeEditorAsync()
{ {
if (_disposed)
{
return;
}
if (_editorId != null && _interop != null) if (_editorId != null && _interop != null)
{ {
await _interop.DestroyEditorAsync(_editorId); await _interop.DestroyEditorAsync(_editorId);
@@ -210,8 +242,18 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
IsReady = false; IsReady = false;
_useFallback = false; _useFallback = false;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
if (_disposed)
{
return;
}
await LoadToolbarItemsAsync(); await LoadToolbarItemsAsync();
await InitializeEditorAsync(); await InitializeEditorAsync();
if (_disposed)
{
return;
}
_lastRenderedDiffMode = IsDiffReview; _lastRenderedDiffMode = IsDiffReview;
_lastRenderedValue = Value; _lastRenderedValue = Value;
_lastRenderedOriginal = OriginalValue; _lastRenderedOriginal = OriginalValue;
@@ -388,14 +430,46 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
_disposed = true;
if (_editorId != null && _interop != null) if (_editorId != null && _interop != null)
{ {
await _interop.DestroyEditorAsync(_editorId); try
{
await _interop.DestroyEditorAsync(_editorId);
}
catch (JSDisconnectedException ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] DestroyEditorAsync JSDisconnected: {ex.Message}");
}
catch (ObjectDisposedException ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] DestroyEditorAsync ObjectDisposed: {ex.Message}");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] DestroyEditorAsync error: {ex}");
}
} }
if (_interop != null) if (_interop != null)
{ {
await _interop.DisposeAsync(); try
{
await _interop.DisposeAsync();
}
catch (JSDisconnectedException ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] Interop DisposeAsync JSDisconnected: {ex.Message}");
}
catch (ObjectDisposedException ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] Interop DisposeAsync ObjectDisposed: {ex.Message}");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[SbMarkdownEditor] Interop DisposeAsync error: {ex}");
}
} }
_dotNetRef?.Dispose(); _dotNetRef?.Dispose();