Skip to content

[QuickAccess] Suppress unhandled XAML exceptions in flyout host - #48457

Merged
Niels Laute (niels9001) merged 2 commits into
microsoft:mainfrom
crutkas:user/crutkas/fix-quickaccess-stowed-exception
Jun 18, 2026
Merged

Niels Laute (niels9001) merged 2 commits into
microsoft:mainfrom
crutkas:user/crutkas/fix-quickaccess-stowed-exception

Conversation

@crutkas

@crutkas Clint Rutkas (crutkas) commented Jun 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds the two missing top-level exception handlers in the QuickAccess (Preview) flyout host so that an unhandled XAML exception during launch or page navigation no longer FailFasts PowerToys.QuickAccess.exe.

Spotted while reading through App.OnLaunched and ShellPage for an unrelated review of the flyout startup path — none of the existing handlers exist yet, so any throw during MainWindow construction, ShellHost.Initialize, or ContentFrame.Navigate(typeof(LaunchPage) | typeof(AppsListPage), …) bubbles all the way out to the Windows App SDK runtime and is stowed as a XAML failure. Compare with src\settings-ui\Settings.UI\SettingsXAML\App.xaml.cs, which already wires UnhandledException += App_UnhandledException.

Changes

src\settings-ui\QuickAccess.UI\QuickAccessXAML\App.xaml.cs

  • Hook Application.UnhandledException in the constructor. The handler logs the exception via ManagedCommon.Logger.LogError (same logger Settings uses) and sets e.Handled = true. QuickAccess is a transient launcher flyout owned by the runner, so swallowing a stray XAML error and keeping the host alive for the next summon is the correct trade-off — the failure is still recorded for diagnostics.
  • Wrap the body of OnLaunched in a try/catch. If MainWindow (which sets up window chrome, listener threads, the IPC coordinator, and the XAML shell) fails to construct, log the exception and call Exit() cleanly rather than letting the throw escape into the Windows App SDK launch path.

src\settings-ui\QuickAccess.UI\QuickAccessXAML\Flyout\ShellPage.xaml.cs

  • Subscribe to ContentFrame.NavigationFailed after InitializeComponent. A page constructor or XAML-load failure in LaunchPage / AppsListPage would otherwise bubble out of the Frame and crash the launcher. The handler logs the failure (SourcePageType.FullName + the exception) and marks it handled so the next summon retries navigation.

No production behaviour changes when things work — only the failure paths are different. No public API surface changes.

Why both handlers, not just one

  • Application.UnhandledException does not fire for Frame.NavigationFailed. The Frame raises its own event first and, if no handler runs or e.Handled is left false, then it rethrows on the dispatcher.
  • Conversely, Frame.NavigationFailed only fires for navigation failures — not for an exception thrown directly in OnLaunched before any navigation happens.

The two events are complementary, so both need a handler to fully cover the launch + navigation paths.

Testing

  • The local NuGet feed on my dev box currently can't restore Microsoft.NETCore.App.Runtime.win-x64 = 10.0.9 (the feed only has 11.0.0-preview.1.26104.118), which fails the project restore for every WinUI project including this one. That's the same environment issue I called out on [Settings] Add navigation smoke test using winapp CLI (no WinAppDriver) #48414 — pipeline restore uses a different feed and is fine.
  • All three patterns added here are copy-paste analogues of code that already exists in Settings.UI (App.xaml.cs:96, 106-109, ShellViewModel.cs:86, 136), so namespace and signature drift risk is minimal. The only behavioural difference is e.Handled = true, which is the actual goal of this PR.

Risk

  • Low. Two new event handlers and one try/catch. No behaviour change on the success path.
  • Worst-case regression is that a real, repeatable XAML failure becomes silent in the runner's eyes (no process crash) instead of loud — but it's logged via Logger.LogError so the user can still find the trace in %LOCALAPPDATA%\Microsoft\PowerToys\Logs\.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com


ADO: https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/61258633/

Clint Rutkas (crutkas) and others added 2 commits June 10, 2026 12:59
PowerToys.QuickAccess.exe has no Application.UnhandledException handler and no Frame.NavigationFailed handler, so any throw during MainWindow construction or page navigation bubbles into the Windows App SDK launch path and FailFasts the process.

- App.xaml.cs: hook Application.UnhandledException (log + set Handled=true) and wrap OnLaunched in a try/catch that logs the failure and calls Exit() if MainWindow cannot be constructed.

- ShellPage.xaml.cs: hook ContentFrame.NavigationFailed (log + set Handled=true) so a LaunchPage or AppsListPage constructor failure no longer takes down the launcher.

Both patterns mirror the existing Settings.UI handlers (App.xaml.cs:96, ShellViewModel.cs:86) and use the same ManagedCommon.Logger sink.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CS0104 ambiguity between Microsoft.UI.Xaml.UnhandledExceptionEventArgs and System.UnhandledExceptionEventArgs (both in scope via the using directives). Matches the Settings.UI App.xaml.cs:106 convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@crutkas Clint Rutkas (crutkas) added Needs-Review This Pull Request awaits the review of a maintainer. bug Something isn't working labels Jun 11, 2026
@niels9001
Niels Laute (niels9001) merged commit ab49475 into microsoft:main Jun 18, 2026
12 checks passed
Boliang Zhang (LegendaryBlair) pushed a commit that referenced this pull request Jun 22, 2026
## Summary

Adds the two missing top-level exception handlers in the QuickAccess
(Preview) flyout host so that an unhandled XAML exception during launch
or page navigation no longer FailFasts `PowerToys.QuickAccess.exe`.

Spotted while reading through `App.OnLaunched` and `ShellPage` for an
unrelated review of the flyout startup path — none of the existing
handlers exist yet, so any throw during `MainWindow` construction,
`ShellHost.Initialize`, or `ContentFrame.Navigate(typeof(LaunchPage) |
typeof(AppsListPage), …)` bubbles all the way out to the Windows App SDK
runtime and is stowed as a XAML failure. Compare with
`src\settings-ui\Settings.UI\SettingsXAML\App.xaml.cs`, which already
wires `UnhandledException += App_UnhandledException`.

## Changes

**`src\settings-ui\QuickAccess.UI\QuickAccessXAML\App.xaml.cs`**

- Hook `Application.UnhandledException` in the constructor. The handler
logs the exception via `ManagedCommon.Logger.LogError` (same logger
Settings uses) and sets `e.Handled = true`. QuickAccess is a transient
launcher flyout owned by the runner, so swallowing a stray XAML error
and keeping the host alive for the next summon is the correct trade-off
— the failure is still recorded for diagnostics.
- Wrap the body of `OnLaunched` in a try/catch. If `MainWindow` (which
sets up window chrome, listener threads, the IPC coordinator, and the
XAML shell) fails to construct, log the exception and call `Exit()`
cleanly rather than letting the throw escape into the Windows App SDK
launch path.

**`src\settings-ui\QuickAccess.UI\QuickAccessXAML\Flyout\ShellPage.xaml.cs`**

- Subscribe to `ContentFrame.NavigationFailed` after
`InitializeComponent`. A page constructor or XAML-load failure in
`LaunchPage` / `AppsListPage` would otherwise bubble out of the `Frame`
and crash the launcher. The handler logs the failure
(`SourcePageType.FullName` + the exception) and marks it handled so the
next summon retries navigation.

No production behaviour changes when things work — only the failure
paths are different. No public API surface changes.

## Why both handlers, not just one

- `Application.UnhandledException` does not fire for
`Frame.NavigationFailed`. The Frame raises its own event first and, if
no handler runs or `e.Handled` is left `false`, then it rethrows on the
dispatcher.
- Conversely, `Frame.NavigationFailed` only fires for navigation
failures — not for an exception thrown directly in `OnLaunched` before
any navigation happens.

The two events are complementary, so both need a handler to fully cover
the launch + navigation paths.

## Testing

- The local NuGet feed on my dev box currently can't restore
`Microsoft.NETCore.App.Runtime.win-x64 = 10.0.9` (the feed only has
`11.0.0-preview.1.26104.118`), which fails the project restore for every
WinUI project including this one. That's the same environment issue I
called out on #48414 — pipeline restore uses a different feed and is
fine.
- All three patterns added here are copy-paste analogues of code that
already exists in `Settings.UI` (`App.xaml.cs:96, 106-109`,
`ShellViewModel.cs:86, 136`), so namespace and signature drift risk is
minimal. The only behavioural difference is `e.Handled = true`, which is
the actual goal of this PR.

## Risk

- Low. Two new event handlers and one try/catch. No behaviour change on
the success path.
- Worst-case regression is that a real, repeatable XAML failure becomes
silent in the runner's eyes (no process crash) instead of loud — but
it's logged via `Logger.LogError` so the user can still find the trace
in `%LOCALAPPDATA%\Microsoft\PowerToys\Logs\`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---

ADO:
https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/61258633/

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit ab49475)
@LegendaryBlair Boliang Zhang (LegendaryBlair) added the Area-Flyout Issues refering to the PowerToys flyout launched from the tray area label Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.100.1 Area-Flyout Issues refering to the PowerToys flyout launched from the tray area bug Something isn't working Needs-Review This Pull Request awaits the review of a maintainer.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants