TabItem.OnPreviewGotKeyboardFocus causes StackOverflowException when ancestor redirects focus
- 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
Assessment
This issue has not been assessed yet.