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
- Host a
TabControl inside a container that has a GotKeyboardFocus handler which redirects keyboard focus to a TabItem when the tab content receives focus.
- Switch tabs (or trigger any action that calls
TabItem.SetFocus()).
- 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.
- 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.
TabItem.OnPreviewGotKeyboardFocus causes StackOverflowException when ancestor redirects focus
Summary
TabItem.OnPreviewGotKeyboardFocuscan enter infinite recursion and crash with aStackOverflowExceptionwhen an ancestor element'sGotKeyboardFocushandler redirects keyboard focus back to theTabItem.Observed behavior
StackOverflowExceptionon the UI thread when a tool window tab receives keyboard focus.TabItem.OnPreviewGotKeyboardFocus→MoveFocus→ChangeFocus→GotKeyboardFocus→ ancestor handler →Keyboard.Focus(tabItem)→TryChangeFocus→PreviewGotKeyboardFocus→ back toOnPreviewGotKeyboardFocus.Repro notes
TabControlinside a container that has aGotKeyboardFocushandler which redirects keyboard focus to aTabItemwhen the tab content receives focus.TabItem.SetFocus()).MoveFocuscall inOnPreviewGotKeyboardFocusmoves focus to the tab's content, which firesGotKeyboardFocuson the content. This event bubbles to the ancestor handler, which redirects focus back to theTabItem, re-enteringOnPreviewGotKeyboardFocus.This pattern occurs in Visual Studio integration scenarios where pane hosting infrastructure participates in focus management.
Impact
Any WPF application hosting a
TabControlinside a container that redirectsGotKeyboardFocusback to tab headers is affected. The crash is deterministic and unrecoverable.Expected behavior
OnPreviewGotKeyboardFocusshould 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
MoveFocuscall unconditionally — even when the tab is already selected. The original code ranMoveFocusinside the!IsSelectedbranch, 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
MovingFocusToContentre-entrancy guard flag (following the existingSettingFocuspattern inBoolField) around theMoveFocus+SetFocusedElementblock.OnPreviewGotKeyboardFocusis re-entered while the flag is set, skip the entire focus-to-content block, breaking the cycle.