-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Rapid change of selected tab results in crash on Windows #32865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,10 @@ public partial class TabbedPage | |||||||||||||
| NavigationRootManager? _navigationRootManager; | ||||||||||||||
| WFrame? _navigationFrame; | ||||||||||||||
| bool _connectedToHandler; | ||||||||||||||
| // Volatile ensures visibility of flag changes across async operations | ||||||||||||||
| // These flags prevent reentrant calls within the UI thread (not multi-threaded access) | ||||||||||||||
| volatile bool _isUpdatingSelection; | ||||||||||||||
| volatile bool _isNavigating; | ||||||||||||||
| WFrame NavigationFrame => _navigationFrame ?? throw new ArgumentNullException(nameof(NavigationFrame)); | ||||||||||||||
| IMauiContext MauiContext => this.Handler?.MauiContext ?? throw new InvalidOperationException("MauiContext cannot be null here"); | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -246,6 +250,9 @@ void UpdateValuesWaitingForNavigationView() | |||||||||||||
|
|
||||||||||||||
| void OnSelectedMenuItemChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) | ||||||||||||||
| { | ||||||||||||||
| if (_isUpdatingSelection) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
| if (args.SelectedItem is NavigationViewItemViewModel itemViewModel && | ||||||||||||||
| itemViewModel.Data is Page page) | ||||||||||||||
| { | ||||||||||||||
|
|
@@ -255,9 +262,13 @@ void OnSelectedMenuItemChanged(NavigationView sender, NavigationViewSelectionCha | |||||||||||||
|
|
||||||||||||||
| void NavigateToPage(Page page) | ||||||||||||||
| { | ||||||||||||||
| if (_isNavigating) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
| FrameNavigationOptions navOptions = new FrameNavigationOptions(); | ||||||||||||||
| CurrentPage = page; | ||||||||||||||
| navOptions.IsNavigationStackEnabled = false; | ||||||||||||||
| _isNavigating = true; | ||||||||||||||
|
Comment on lines
269
to
+271
|
||||||||||||||
| CurrentPage = page; | |
| navOptions.IsNavigationStackEnabled = false; | |
| _isNavigating = true; | |
| navOptions.IsNavigationStackEnabled = false; | |
| _isNavigating = true; | |
| CurrentPage = page; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix lacks test coverage for the rapid tab switching scenario that caused the original crash. Given that:
- The repository has comprehensive test coverage for TabbedPage (as seen in
TabbedPageTests.Windows.cs) - This is a critical bug fix for a crash scenario
A test should be added to verify that rapid successive tab changes don't cause a crash. The test should simulate clicking multiple tabs in quick succession before navigation completes, similar to:
[Fact(DisplayName = "Rapid Tab Switching Does Not Crash")]
public async Task RapidTabSwitchingDoesNotCrash()
{
SetupBuilder();
var tabbedPage = CreateBasicTabbedPage();
tabbedPage.Children.Add(new ContentPage { Title = "Page 2" });
tabbedPage.Children.Add(new ContentPage { Title = "Page 3" });
await CreateHandlerAndAddToWindow<TabbedViewHandler>(tabbedPage, async handler =>
{
var navView = GetMauiNavigationView(handler.MauiContext);
var items = (navView.MenuItemsSource as IEnumerable<NavigationViewItemViewModel>).ToList();
// Rapidly switch tabs without waiting
navView.SelectedItem = items[1];
navView.SelectedItem = items[2];
navView.SelectedItem = items[0];
// Wait for navigation to complete and verify no crash
await Task.Delay(500);
Assert.Equal(tabbedPage.Children[0], tabbedPage.CurrentPage);
});
}This test should be added to src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs.
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The try-finally block in OnNavigated ensures _isNavigating is always reset, which is good. However, if UpdateCurrentPageContent throws an exception, the navigation will be marked as complete (_isNavigating = false) even though the content update failed.
This could lead to an inconsistent state where:
- Navigation is marked complete
- But the UI doesn't show the correct content
- Future navigation attempts proceed, potentially compounding the error
Consider whether exceptions from UpdateCurrentPageContent should be caught, logged, and handled explicitly rather than allowing them to propagate while still clearing the flag. At minimum, the existing behavior should be documented with a comment explaining the trade-off.
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MapCurrentPage method doesn't break out of the loop after finding and setting the selected item. While functionally this works because only one item should match view.CurrentPage, it unnecessarily iterates through all remaining items after a match is found.
Suggested optimization:
foreach (var item in items)
{
if (item.Data == view.CurrentPage)
{
view._navigationView.SelectedItem = item;
break; // Exit loop once we've found and set the item
}
}| view._navigationView.SelectedItem = item; | |
| view._navigationView.SelectedItem = item; | |
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of
volatilehere is misleading. The comment states these flags are for preventing reentrant calls within the UI thread, not for multi-threaded access. However,volatileis a threading construct that ensures visibility across threads.Since this is single-threaded UI code with async operations,
volatileprovides no benefit and creates confusion. The flags should simply beboolwithout thevolatilemodifier. The comment should also be simplified to remove the confusing threading reference.