dotnet / dotnet/wpf

TabItem.OnPreviewGotKeyboardFocus causes StackOverflowException when ancestor redirects focus

Open
#11,679 1 comment 0 reactions 0 assignees View on GitHub
:mailbox_with_no_mail: waiting-author-feedback Bug PR Proposed Priority:1 regression
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

# TabItem.OnPreviewGotKeyboardFocus causes StackOverflowException when ancestor redirects focus

## Summary

`TabItem.OnPreviewGotKeyboardFocus` can enter infinite recursion and crash with a `StackOverflowException` when an ancestor element's `GotKeyboardFocus` handler redirects keyboard focus back to the `TabItem`.

## Observed behavior

- Application crashes with `StackOverflowException` on the UI thread when a tool window tab receives keyboard focus.
- The call stack shows ~90+ recursive frames cycling through `TabItem.OnPreviewGotKeyboardFocus` → `MoveFocus` → `ChangeFocus` → `GotKeyboardFocus` → ancestor handler → `Keyboard.Focus(tabItem)` → `TryChangeFocus` → `PreviewGotKeyboardFocus` → back to `OnPreviewGotKeyboardFocus`.

## Repro notes

1. Host a `TabControl` inside a container that has a `GotKeyboardFocus` handler which redirects keyboard focus to a `TabItem` when the tab content receives focus.
2. Switch tabs (or trigger any action that calls `TabItem.SetFocus()`).
3. The `MoveFocus` call in `OnPreviewGotKeyboardFocus` moves focus to the tab's content, which fires `GotKeyboardFocus` on the content. This event bubbles to the ancestor handler, which redirects focus back to the `TabItem`, re-entering `OnPreviewGotKeyboardFocus`.
4. The cycle repeats until the stack overflows.

This pattern occurs in Visual Studio integration scenarios where pane hosting infrastructure participates in focus management.

## Impact

Any WPF application hosting a `TabControl` inside a container that redirects `GotKeyboardFocus` back to tab headers is affected. The crash is deterministic and unrecoverable.

## Expected behavior

`OnPreviewGotKeyboardFocus` should handle re-entrant calls gracefully without infinite recursion.

## Actual behavior

Infinite recursion → `StackOverflowException` → process crash.

## Suspected root cause

The focus-into-content logic added by #8300 (for #8293) runs the `MoveFocus` call unconditionally — even when the tab is already selected. The original code ran `MoveFocus` inside the `!IsSelected` branch, where re-entrancy couldn't occur because the tab would already be selected on the second pass. The new code path has no such protection.

## Proposed fix direction

- Add a `MovingFocusToContent` re-entrancy guard flag (following the existing `SettingFocus` pattern in `BoolField`) around the `MoveFocus` + `SetFocusedElement` block.
- When `OnPreviewGotKeyboardFocus` is re-entered while the flag is set, skip the entire focus-to-content block, breaking the cycle.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.