Thanks to visit codestin.com
Credit goes to github.com

Skip to content

MudMenu: Fix JSInterop focus crash on WebView when closing/navigating#13317

Merged
danielchalmers merged 2 commits into
MudBlazor:devfrom
danielchalmers:claude/pedantic-moore-6ab1ac
Jun 19, 2026
Merged

MudMenu: Fix JSInterop focus crash on WebView when closing/navigating#13317
danielchalmers merged 2 commits into
MudBlazor:devfrom
danielchalmers:claude/pedantic-moore-6ab1ac

Conversation

@danielchalmers

Copy link
Copy Markdown
Member

Fixes #12184

Problem

MudMenu throws Microsoft.JSInterop.JSException: Unable to focus an invalid element on MAUI BlazorWebView (iOS/Android) when the menu is closed or the page is navigated while the menu is open. Reported repro paths: navigate while open, rapidly open/close an overflow menu, and keyboard submenu navigation. It only manifests on the WebView host — desktop browsers don't crash.

Cause

A regression introduced in v8.14.0 by #11762, which added an OnAfterRenderAsync override that focuses _menuWrapperRef (and, via FocusItemAsync, menu items). Before that PR, MudMenu had no focus interop at all.

The …Context is not null guards on those calls are not liveness checksElementReference.Context stays non-null after the underlying DOM node is detached (TOCTOU). So the deferred focus interop runs against a missing element and Blazor's domWrapper.focus throws. It only reproduces on the WebView host because its native↔WebView message bridge is asynchronous, so the queued focus call can land after the close/navigation render batch removes the element; desktop browsers win that race.

Tellingly, the same PR already guards FocusActivatorAsync with try/catch (JSException) but omitted equivalent handling on the two new focus calls.

Fix

  • Wrap the wrapper focus in OnAfterRenderAsync and the item focus in FocusItemAsync in try/catch (JSException), matching the existing FocusActivatorAsync / CloseMenuAsync idiom in this file. (JSDisconnectedException derives from JSException, so circuit-disconnect is covered too.) This handles the rapid open/close path, which closes without disposing.
  • Add a _disposed flag set in Dispose(bool) and checked in OnAfterRenderAsync and FocusItemAsync, so the fire-and-forget focus calls scheduled by TrackKeyboardInteraction short-circuit after teardown instead of raising an unobserved exception. This handles the navigate-while-open path. Because every keyboard navigation path funnels through FocusItemAsync, hardening that one method covers all of them.

Tests

Added three regression tests in MenuTests that drive Blazor._internal.domWrapper.focus to throw "Unable to focus an invalid element":

  • OpenMenu_WhenFocusInteropThrows_DoesNotCrash — the OnAfterRenderAsync wrapper-focus path (the exact reported stack frame).
  • FocusItemAsync_WhenFocusInteropThrows_DoesNotPropagate — the keyboard focus funnel.
  • FocusItemAsync_AfterDispose_DoesNotInvokeFocusInterop — the _disposed guard.

Verified: with the fix reverted, exactly these 3 tests fail (93/96 pass); with the fix, all 96 MenuTests pass.

⚠️ Note: bUnit cannot reproduce the WebView timing race itself — the tests throw the JSException deterministically to lock in the guard behavior. Final validation should be a manual pass on a real MAUI BlazorWebView device (a desktop browser will always look fine because it wins the race).

Checklist:

  • I've read the contribution guidelines
  • My code follows the style of this project
  • I've added or updated relevant unit tests

MudMenu threw `JSException: Unable to focus an invalid element` on MAUI
BlazorWebView (iOS/Android) when the menu was closed or the page was
navigated while the menu was open. This was a regression introduced in
v8.14.0 by PR MudBlazor#11762, which added an `OnAfterRenderAsync` override that
focuses `_menuWrapperRef` (and, via `FocusItemAsync`, menu items).

The `_menuWrapperRef.Context is not null` / `elementRef.Context is not null`
guards are not liveness checks: `ElementReference.Context` stays non-null
after the underlying DOM node is detached, so the deferred focus interop
runs against a missing element and Blazor's `domWrapper.focus` throws. It
only reproduces on the WebView host because its native<->WebView message
bridge is asynchronous, so the queued focus call can land after the
close/navigation render batch removes the element; desktop browsers win
that race. The same PR already guards `FocusActivatorAsync` with
`try/catch (JSException)` but omitted equivalent handling on the two new
calls.

Fix:
- Wrap the wrapper focus in `OnAfterRenderAsync` and the item focus in
  `FocusItemAsync` in `try/catch (JSException)`, matching the existing
  `FocusActivatorAsync`/`CloseMenuAsync` idiom (covers the rapid
  open/close path, which closes without disposing).
- Add a `_disposed` guard set in `Dispose(bool)` and checked in
  `OnAfterRenderAsync` and `FocusItemAsync`, so the fire-and-forget focus
  calls scheduled by `TrackKeyboardInteraction` short-circuit after
  teardown instead of raising an unobserved exception (covers the
  navigate-while-open path).

Adds regression tests that drive the focus interop to throw and assert the
menu neither crashes on open nor propagates the exception, plus a test that
`FocusItemAsync` is a no-op after disposal.

Note: bUnit cannot reproduce the WebView timing race itself; the tests lock
in the guard behavior. Final validation requires a real MAUI BlazorWebView
device.

Fixes MudBlazor#12184
@mudbot mudbot Bot added blazor: hybrid Only occurs with Blazor Hybrid apps (.NET MAUI, Electron, or other WebView-based hosts) bug Unexpected behavior or functionality not working as intended regression Previously worked and now doesn't labels Jun 19, 2026
@danielchalmers danielchalmers requested a review from Copilot June 19, 2026 00:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens MudMenu focus-related JS interop against WebView timing races (MAUI BlazorWebView) by preventing focus calls from crashing when the target element is already detached/disposed, and adds regression tests covering the failure modes reported in #12184.

Changes:

  • Add a _disposed flag and short-circuit focus paths (OnAfterRenderAsync, FocusItemAsync) after teardown.
  • Wrap wrapper-focus and item-focus JS interop calls in try/catch (JSException) to swallow “Unable to focus an invalid element” when the DOM node is gone.
  • Add three MenuTests regressions that deterministically throw the underlying focus JS interop exception and assert it’s swallowed.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/MudBlazor/Components/Menu/MudMenu.razor.cs Adds disposal guarding and JSException handling around focus interop to prevent WebView crashes on close/navigation.
src/MudBlazor.UnitTests/Components/MenuTests.cs Adds regression tests to ensure focus interop exceptions don’t propagate and no focus interop runs after dispose.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/MudBlazor/Components/Menu/MudMenu.razor.cs
Addresses Copilot review feedback on MudBlazor#13317. A hovered menu holds a live
CancellationTokenSource, so a second Dispose() (e.g. user code disposing a
@ref before the renderer tears the component down) would call Cancel() on
the already-disposed CTS and throw ObjectDisposedException.

Return early when already disposed. Adds a regression test that hovers to
create the CTS, then disposes twice and asserts no exception.
@danielchalmers danielchalmers merged commit 3fcb345 into MudBlazor:dev Jun 19, 2026
7 of 8 checks passed
@danielchalmers danielchalmers deleted the claude/pedantic-moore-6ab1ac branch June 19, 2026 00:42
This was referenced Jun 27, 2026
This was referenced Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blazor: hybrid Only occurs with Blazor Hybrid apps (.NET MAUI, Electron, or other WebView-based hosts) bug Unexpected behavior or functionality not working as intended regression Previously worked and now doesn't

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MudMenu in MAUI BlazorWebView (InteractiveAuto) crashes with JSInterop ‘Unable to focus an invalid element’ when closing/navigating

2 participants