🐛 Bug Report
Opening the Custom splash screen demo on the docs site throws an unhandled Microsoft.JSInterop.JSException (JS object instance with Id 'NNN' does not exist. It may have been disposed.) when the splash screen auto-closes. The crash originates in FluentDialogProvider.ReturnFocusAsync, which invokes focusElement on a PreviouslyFocusedElement IJSObjectReference that has already been disposed.
💻 Repro or Code Sample
Live repro on the documentation site:
- Go to https://www.fluentui-blazor.net/SplashScreen
- In the Custom splash screen section, click Open splash screen.
- Wait for the splash screen to run (~5–7 seconds) and auto-close.
- Open the browser dev-tools console — an unhandled exception is logged (twice).
The demo is driven by DialogSplashScreenCustom + CustomSplashScreen:
// DialogSplashScreenCustom.razor.cs
private async Task OpenSplashCustomAsync()
{
DialogParameters<SplashScreenContent> parameters = new()
{
Content = new SplashScreenContent()
{
Title = "Water drinking 101",
LoadingText = "Filling the re-useable bottles...",
Message = (MarkupString)"Don't drink <strong>too</strong> much water!",
Logo = "_content/FluentUI.Demo.Shared/images/Splash_Corporation_logo.png",
DisplayTime = 7000
},
Width = "500px",
Height = "300px",
};
_dialog = await DialogService.ShowSplashScreenAsync<CustomSplashScreen>(parameters);
for (var i = 0; i < 5; i++)
{
await Task.Delay(1000);
parameters.Content.LoadingText = $"Filling the re-useable bottles... {i + 1}";
await DialogService.UpdateDialogAsync(_dialog.Id, parameters); // updates dialog 5x
}
DialogResult result = await _dialog.Result;
await HandleCustomSplashAsync(result);
}
// CustomSplashScreen.razor
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Task.Delay(Content.DisplayTime); // 7000ms
await Dialog.CloseAsync(); // <-- self-close triggers the crash
}
}
🤔 Expected Behavior
The custom splash screen closes cleanly after its DisplayTime and returns focus to the trigger element without throwing.
😯 Current Behavior
When the dialog self-closes, FluentDialog.CloseAsync() calls DialogContext.DialogContainer.ReturnFocusAsync(Instance.PreviouslyFocusedElement), which invokes focusElement on an IJSObjectReference that has already been disposed. Two unhandled exceptions are logged in succession (JS object Id 264 and 265):
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: JS object instance with Id '264' does not exist. It may have been disposed.
Microsoft.JSInterop.JSException: JS object instance with Id '264' does not exist. It may have been disposed.
at Microsoft.JSInterop.JSRuntime.InvokeAsync[IJSVoidResult](...)
at Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeVoidAsync(IJSObjectReference , String , Object[] )
at Microsoft.FluentUI.AspNetCore.Components.FluentDialogProvider.ReturnFocusAsync(IJSObjectReference element)
at Microsoft.FluentUI.AspNetCore.Components.FluentDialog.CloseAsync(DialogResult dialogResult)
at Microsoft.FluentUI.AspNetCore.Components.FluentDialog.CloseAsync()
at FluentUI.Demo.Shared.Pages.SplashScreen.Examples.CustomSplashScreen.OnAfterRenderAsync(Boolean firstRender)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task , ComponentState )
A second identical exception follows for JS object Id 265.
💁 Possible Solution
The relevant source paths:
FluentDialog.razor.cs (CloseAsync) returns focus to the captured element:
if (DialogContext is not null && Instance.PreviouslyFocusedElement is not null)
{
await Task.Delay(50);
await DialogContext.DialogContainer.ReturnFocusAsync(Instance.PreviouslyFocusedElement);
}
FluentDialogProvider.razor.cs (ReturnFocusAsync) invokes JS then disposes the reference:
internal async Task ReturnFocusAsync(IJSObjectReference element)
{
if (_module is not null)
{
await _module.InvokeVoidAsync("focusElement", element); // throws if element already disposed
}
await element.DisposeAsync();
}
By the time the splash screen self-closes, PreviouslyFocusedElement is a stale IJSObjectReference that has already been disposed — the repeated UpdateDialogAsync re-render cycle appears to leave the reference in a disposed state. Suggestions:
- Guard
ReturnFocusAsync against a disposed/stale reference, e.g. wrap the focusElement interop in a try/catch for JSException/JSDisconnectedException/ObjectDisposedException so a failed focus-return never surfaces as an unhandled render exception.
- Ensure
PreviouslyFocusedElement is not disposed (or is re-captured) across UpdateDialogAsync calls, and null it out after it is disposed to avoid a double-invoke/double-dispose (the two consecutive errors suggest the reference is being used after disposal more than once).
Happy to contribute a fix if the maintainers agree on the preferred approach.
🔦 Context
The Custom splash screen documentation example crashes on the public docs site, which is a poor first impression for anyone evaluating the splash screen feature. The same pattern (a self-closing dialog combined with UpdateDialogAsync) can surface in real apps.
🌍 Your Environment
- OS & Device: Reproduced on the public docs site (any desktop OS/browser)
- Browser: Google Chrome (dev-tools console)
- .NET and Fluent UI Blazor library Version: Blazor WebAssembly; JSInterop
Version=10.0.0.0 (.NET 10) per the stack trace. Vulnerable code confirmed present on dev at v4.14.3.
- URL: https://www.fluentui-blazor.net/SplashScreen
🐛 Bug Report
Opening the Custom splash screen demo on the docs site throws an unhandled
Microsoft.JSInterop.JSException(JS object instance with Id 'NNN' does not exist. It may have been disposed.) when the splash screen auto-closes. The crash originates inFluentDialogProvider.ReturnFocusAsync, which invokesfocusElementon aPreviouslyFocusedElementIJSObjectReferencethat has already been disposed.💻 Repro or Code Sample
Live repro on the documentation site:
The demo is driven by
DialogSplashScreenCustom+CustomSplashScreen:🤔 Expected Behavior
The custom splash screen closes cleanly after its
DisplayTimeand returns focus to the trigger element without throwing.😯 Current Behavior
When the dialog self-closes,
FluentDialog.CloseAsync()callsDialogContext.DialogContainer.ReturnFocusAsync(Instance.PreviouslyFocusedElement), which invokesfocusElementon anIJSObjectReferencethat has already been disposed. Two unhandled exceptions are logged in succession (JS object Id264and265):A second identical exception follows for JS object Id
265.💁 Possible Solution
The relevant source paths:
FluentDialog.razor.cs(CloseAsync) returns focus to the captured element:FluentDialogProvider.razor.cs(ReturnFocusAsync) invokes JS then disposes the reference:By the time the splash screen self-closes,
PreviouslyFocusedElementis a staleIJSObjectReferencethat has already been disposed — the repeatedUpdateDialogAsyncre-render cycle appears to leave the reference in a disposed state. Suggestions:ReturnFocusAsyncagainst a disposed/stale reference, e.g. wrap thefocusElementinterop in atry/catchforJSException/JSDisconnectedException/ObjectDisposedExceptionso a failed focus-return never surfaces as an unhandled render exception.PreviouslyFocusedElementis not disposed (or is re-captured) acrossUpdateDialogAsynccalls, and null it out after it is disposed to avoid a double-invoke/double-dispose (the two consecutive errors suggest the reference is being used after disposal more than once).Happy to contribute a fix if the maintainers agree on the preferred approach.
🔦 Context
The Custom splash screen documentation example crashes on the public docs site, which is a poor first impression for anyone evaluating the splash screen feature. The same pattern (a self-closing dialog combined with
UpdateDialogAsync) can surface in real apps.🌍 Your Environment
Version=10.0.0.0(.NET 10) per the stack trace. Vulnerable code confirmed present ondevatv4.14.3.