fix(win32): Make SystemBackdrop work and stop rewriting app backgrounds (6.7) - #24810
MartinZikmund wants to merge 22 commits into
Conversation
Setting Window.SystemBackdrop walked the entire visual tree and replaced
the Background of every Panel, Border, ContentPresenter and Control that
had an opaque SolidColorBrush with a transparent one, re-walking on every
Loaded to catch newly navigated content.
That is not what WinUI does, and it is observable from app code: a Grid
explicitly painted red read back as Transparent afterwards. It also wrote
those brushes with Local precedence, so any {ThemeResource} background it
touched stopped re-resolving on a theme change, and it kept a Loaded and
Unloaded subscription on every FrameworkElement it had ever visited.
WinUI instead flips a single flag on the island root
(CXamlIslandRoot::SetHasTransparentBackground, from
DesktopWindowImpl::SetXamlIslandRootBackground) and the render walk skips
that one element's background primitive
(BaseContentRenderer::PanelRenderContent). The Panel.Background property
keeps its theme brush throughout; app content is never touched.
Ports the same flag onto XamlIslandRoot, suppressing the background push
in BorderHelper.UpdateBackground - Uno's equivalent of the MUX render
walk - and deletes the traversal and its eight helpers.
BREAKING CHANGE: Content backgrounds are no longer made transparent when
a SystemBackdrop is set. An app that relied on the framework doing this
must make its own page or root panel transparent for the material to show
through, which is what WinUI has always required.
Verified on Skia Win32, client-area centre pixel:
Mica + background-less Grid material visible, island root's
Background still #FF000000
Mica + Grid painted red #FF0000 (WinUI: #FF0000; was: material)
no backdrop, light / dark #FFFFFF / #000000, unchanged
Still to do when this lands on feature/breakingchanges: add the migration
entry to doc/articles/migrating-to-uno-7.md, which does not exist on this
branch's base.
DWMWA_USE_IMMERSIVE_DARK_MODE was set from the desktop theme, so an app running light on a dark desktop got a dark Mica or Acrylic material. MUX drives SystemBackdropConfiguration.Theme from XamlRoot.Content.ActualTheme (SystemBackdrop_Partial.cpp), so WinUI shows a light material there. DWM exposes one per-window flag for both the material tint and the caption colour, so this follows the content's ActualTheme, falling back to Application.RequestedTheme when there is no content yet, and re-applies when the content or the desktop theme changes. Light app on a dark desktop, client-area centre pixel: Mica before #221F20 after #F8F1F2 WinUI #F5F1F7 Acrylic before dark after light WinUI light Dark-on-dark is unchanged (#231F1D, WinUI #211F21), and windows without a backdrop keep #FFFFFF / #000000. Side effect worth knowing: the caption follows suit, because it is the same DWM flag. A light-themed app now gets a light caption, which is what WinUI shows. A dark-themed app gets a dark caption, where WinUI shows a light one - WinUI never opts a window into DWM dark mode at all, so its caption is light regardless of theme, and apps override it by hand.
The root element's background was computed twice by two different rules. UnoRootElementLogic seeded it at construction from ThemingHelper.GetRootVisualBackground(), plain White/Black off Application.RequestedTheme with no high-contrast handling, while CoreServices.NotifyThemeChange re-applied it on every theme change from FrameworkTheming.GetHwndBackground(Theme.None), which returns GetSystemColor(COLOR_WINDOW) under high contrast. A high-contrast app therefore started with plain white or black under its content and only converged on the correct colour at the first theme notification. MUX has no such split: CXamlIslandRoot::NotifyThemeChangedCore is called from InitializeCommon() and from every CCoreServices:: NotifyThemeChange, so both values come from one function by construction. Seeds the island root inside InitializeRoot and the CoreWindow content root in InitCoreWindowContentRoot, both from FrameworkTheming - the same source the theme-change path already uses - and drops the second formula. Both seed points hold a CoreServices instance already, so this adds no singleton access during CoreServices construction. Unchanged on Skia Win32: #FFFFFF light, #000000 dark, with and without a backdrop, for Grid, Page, Border, UserControl and null content.
Adds two runtime tests for the WinUI contract that the island-root flag implements: - setting HasTransparentBackground suppresses only the rendering, leaving Panel.Background holding its theme brush, so a later theme change still resolves against it; - setting and clearing Window.SystemBackdrop leaves app content's own Background untouched, guarding the removed tree walk from coming back. Also drops a stale 'Verify that center of window is red' comment in VerifyWindowBackgroundAsync, which asserts a brush rather than a pixel.
WaitForLoaded requires a non-zero ActualWidth/ActualHeight, so the empty Border and Grid never satisfied it. Gives both an explicit size, and hosts them on the main window rather than a secondary one - Given_Window's secondary-window tests currently tear down the render thread hard enough to take the whole process with them on Skia Win32, which loses the results file.
The island root dropped its opaque background whenever MicaController.IsSupported() agreed, but that answers for the operating system, not for the head. Headless, X11, FrameBuffer, Android, iOS and WASM all inherit a no-op SetSystemBackdrop, so on Windows 11 they passed the OS check, went transparent, and rendered no material behind the root. Ask the native window instead, via INativeWindowWrapper.IsSystemBackdropSupported. Heads that cannot render a material keep the themed root background, which is Uno's equivalent of the solid FallbackColor MUX's MicaController paints when the material is unavailable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbdjkceVUCdxeEEZkK1haR
Fails against the previous OS-version check on Windows 11: the unit-test host's window wrapper renders no material, yet MicaController.IsSupported() reported support and the island root went transparent over nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbdjkceVUCdxeEEZkK1haR
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbdjkceVUCdxeEEZkK1haR
An unsupported backdrop left the island root painting the root visual's colour, which is flat black or white - the legacy UWP value, normally invisible because app content covers it. A window that asked for a material and got that instead looks unfinished. Paint ApplicationPageBackgroundThemeBrush in that state. Under the Fluent styles it resolves to SolidBackgroundFillColorBase (#202020 dark, #F3F3F3 light) - the colours WinUI 3 shows - and to the system window colour under high contrast. This is Uno's analogue of MicaController.FallbackColor, which is what MUX paints when the material cannot be rendered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbdjkceVUCdxeEEZkK1haR
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbdjkceVUCdxeEEZkK1haR
MUX's SystemBackdrop takes its theme from XamlRoot.Content.ActualTheme and moves that subscription to the new content on XamlRoot.Changed. Uno only re-read it on system-theme or backdrop changes, so replacing or clearing Window.Content left the DWM caption/tint on the old element (and kept it alive), and the unsupported-head fallback resolved against the app theme. Window now raises an internal ContentChanged; the Win32 frame theme and the island root's fallback brush both retarget on it and on ActualThemeChanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
A Window constructed before Application.InitializationCompleted gets its native wrapper later, in Initialize(). A SystemBackdrop assigned in between hit a null NativeWrapper and never reached the native window, while the root had already switched to its backdrop background. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
A backdrop type Win32 cannot render makes the root paint its opaque fallback, but SetSystemBackdrop returned early and left the previous Mica/Acrylic under the frame. Clear it with DWMSBT_NONE and recompute the frame extension, as the macOS head already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
The fallback test resolved the expected brush but never compared it with anything. Compare it with what the root's BorderVisual actually renders (BackgroundBrush getter made internal for that), and cover the fallback following the content's RequestedTheme and a replaced Window.Content. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
Clear the main window content in finally so the headless runner does not carry it into later tests, and link both tests to #24189. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019C454B8usXZRfvcjWT1BB4
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHHcu5U9N25wvKGNFCrpJm
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHHcu5U9N25wvKGNFCrpJm
The 7.0 migration guide carries this note on master; 6.x has no such guide, so it lives on the SystemBackdrop feature page instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XU3kKG9jHwupx4TefeXQAe
6.x still builds Uno.UI for Reference and the native renderers, which have no BorderVisual and so no UpdateBackground extension. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XU3kKG9jHwupx4TefeXQAe
|
Claude finished @MartinZikmund's task in 7m 49s —— View job PR Review —
|
| # | File | Severity | Summary |
|---|---|---|---|
| 1 | Window.cs:397 |
Minor | _backdropThemeSource.ActualThemeChanged is never unsubscribed on Close() — Win32WindowWrapper.OnWmDestroy has the symmetric cleanup for _frameThemeSource but Window has no equivalent |
| 2 | Window.cs:564-570 |
Low | UpdateBackground is called twice in NotifyContentLoaded on the Fallback path — idempotent and harmless, but redundant |
| 3 | MacOSWindowWrapper.cs:108 |
Low | IsSystemBackdropSupported returns true unconditionally for Mica/Acrylic without a macOS version or capability guard (Win32 correctly gates on MicaController.IsSupported()); on old macOS the island root would go transparent over no material |
| 4 | BorderHelper.skia.cs:50 |
Low | CoreServices.Instance on the fallback path could instantiate a new CoreServices in a post-teardown render walk — HasInstance guard is the conventional fix |
A bonus informational note was left on Given_Window_SystemBackdrop.cs:65 about mutating the global app.HostView theme in When_Content_Theme_Differs_Then_Fallback_Follows_Content (MSTest is serial so it's safe, but worth an eye if that changes).
What's good
- Correct WinUI parity: only the island root's background primitive is suppressed;
Panel.Backgroundkeeps its theme brush — exactly whatBaseContentRenderer::PanelRenderContentdoes in MUX. - Single-source seeding:
XamlIslandRoot.InitializeRootnow seeds fromTheming.GetRootVisualBackground()(same source asCoreServices.NotifyThemeChange), closing the high-contrast startup divergence. - Theme-tracking: both the DWM frame tint (
Win32WindowWrapper._frameThemeSource) and the fallback colour (Window._backdropThemeSource) subscribe toContent.ActualThemeChangedand retarget onContentChanged— correct MUX analog. - Replay on late init: the
Window.Initializereplay (if (_systemBackdrop is not null) ApplySystemBackdrop(...)) handles the race where a backdrop is set before the native window exists. DWMSBT_NONEon unsupported types:SetSystemBackdropclears the DWM backdrop for unknown backdrop types rather than silently leaving a stale material.#if UNO_HAS_BORDER_VISUALguards:SetBackdropBackground/RefreshBackdropBackgroundbodies are correctly gated so the Reference flavor compiles clean.- Conventional commits and test coverage (3/3 unit tests, 2 runtime tests, pixel-validated on master PR).
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24810/wasm-skia-net9/index.html |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-24810/docs/index.html |
|
The build 236212 did not find any UI Test snapshots differences. |
|
|
Uno no longer rewrites opaque content backgrounds when a SystemBackdrop is set (unoplatform/uno#24810), so the page must be transparent to show the material, as in WinUI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7PsEU6gSpLZXrYuK8WWpt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7PsEU6gSpLZXrYuK8WWpt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7PsEU6gSpLZXrYuK8WWpt
|
Claude finished @MartinZikmund's task in 6m 20s —— View job PR Review —
|
| Prior finding | Resolution |
|---|---|
_backdropThemeSource not unsubscribed on close |
Fixed — Closed += OnClosedReleaseBackdropThemeSource wired at Window.cs:117 |
CoreServices.Instance could create an instance in teardown |
Fixed — CoreServices.HasInstance guard added in BorderHelper.skia.cs:50 |
MacOSWindowWrapper.IsSystemBackdropSupported has no OS version guard |
Addressed — comment at MacOSWindowWrapper.cs:108 explains the native library's 10.15+ minimum |
Unit test mutating global app.HostView theme |
Fixed — commit 43b80acd uses an isolated content host |
New finding (informational, does not block)
MicaBackdropTests.xaml.cs, SetBackdrop method — the inline comment still says "the framework transparentizes the visual tree so the material shows through", which was the old behavior this PR removed. The page correctly has Background="Transparent" in XAML, so the behaviour is right, but the comment contradicts the stated PR goal and will mislead developers reading the sample. Worth a one-liner update:
// Setting Window.SystemBackdrop is enough to activate the native material. The framework no
// longer rewrites content backgrounds (that was the old behavior). The Page sets
// Background="Transparent" so the material shows through — the same change WinUI requires.What's good
- Root cause fix is correct and tight:
XamlIslandRoot.SetBackdropBackgroundsuppresses only the island root's rendering primitive, leavingPanel.Backgroundintact — the exact WinUI analog ofCXamlIslandRoot::SetHasTransparentBackground. BackdropBackgroundMode.Fallbackpath paintsApplicationPageBackgroundThemeBrushviaCoreServices.LookupThemeResource, following the content'sActualThemeand updating on theme change — equivalent to MUX'sMicaController.FallbackColor.FrameworkTheming.GetRootVisualBackground()used in both the construction-time seed (XamlIslandRoot.InitializeRoot) and theNotifyThemeChangepath — single source, no high-contrast startup divergence on 6.7.- Replay on late init (
Window.NotifyContentLoaded) is correct; backdrop set before the native window is created is re-applied once the root visual becomes available. #if UNO_HAS_BORDER_VISUALgates compile-clean on Reference and native flavors.- Unit tests 3/3 passing; runtime test coverage for the backdrop background invariants and content-background-is-untouched scenario.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The issue link format and contradictory sample guidance should be corrected before approval.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Backports SystemBackdrop fixes to 6.7, preserving application backgrounds while adding theme-aware native materials and fallbacks.
Changes:
- Suppresses only the island-root background instead of rewriting content.
- Tracks content theme and handles unsupported or deferred backdrops.
- Adds tests, sample guidance, and documentation.
Review verdict: Fix-first — one metadata requirement and one minor documentation issue. The PR description must use the fully qualified issue URL, and the sample retains a contradictory comment.
| File | Description |
|---|---|
Window.cs |
Manages backdrop lifecycle, fallback theme, and content changes. |
NativeWindowWrapperBase.cs |
Defaults backdrop support to false. |
INativeWindowWrapper.cs |
Adds native backdrop capability query. |
ThemingHelper.cs |
Converts ARGB values to colors. |
UnoRootElementLogic.cs |
Removes duplicate root-background initialization. |
XamlIslandRoot.Core.cs |
Adds backdrop background modes. |
CoreServices.Theming.cs |
Uses framework theming for root colors. |
CoreServices.cs |
Unifies initial root-color calculation. |
BorderHelper.skia.cs |
Renders transparent or themed fallback roots. |
Given_Window_SystemBackdrop.cs |
Tests fallback and content-theme behavior. |
Given_Window.cs |
Tests preserved content backgrounds. |
Given_AlcContentHost.cs |
Tests ALC content-change notification. |
Win32WindowWrapper.cs |
Applies content theme and clears unsupported materials. |
Win32WindowWrapper.OverlappedPresenter.cs |
Uses native support for frame extension. |
MacOSWindowWrapper.cs |
Reports supported macOS materials. |
BorderVisual.skia.cs |
Exposes background brush internally for tests. |
MicaBackdropTests.xaml.cs |
Updates sample description. |
MicaBackdropTests.xaml |
Explains transparent-content requirements. |
system-backdrop.md |
Documents behavior change and fallbacks. |
| "On macOS, uses NSVisualEffectView with the matching vibrancy material. " + | ||
| "On Windows 11 (22621+), uses DwmSetWindowAttribute with DWMWA_SYSTEMBACKDROP_TYPE. " + | ||
| "The framework makes the visual tree transparent so the backdrop shows through. " + | ||
| "Setting a backdrop drops the window's own root background; app content keeps its own, so this page sets Background=\"Transparent\". " + |
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-24810/wasm-skia-net9/index.html |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-24810/docs/index.html |
|
The build 236889 did not find any UI Test snapshots differences. |
|
|

GitHub Issue: closes #24189
PR Type:
🐞 Bugfix
What changed? 🚀
Backport of #24190 to
release/stable/6.7, with the same commits as the 6.8 backport #24806. See #24190 for the full analysis and the WinUI 3 pixel comparison. In short:SolidColorBrushBackgroundwith a transparent one (aGridpainted red read back asTransparent, and{ThemeResource}backgrounds stopped updating on theme change). As in MUX (CXamlIslandRoot::SetHasTransparentBackground), only the island root's background primitive is skipped, andPanel.Backgroundkeeps its theme brush.ActualTheme(MUXSystemBackdrop_Partial.cpp) and moves with it whenWindow.Contentis replaced or cleared.FrameworkTheming, so a high-contrast app no longer starts on plain white/black.ApplicationPageBackgroundThemeBrushunder the content's theme) instead of going transparent over nothing, the equivalent of WinUI'sMicaController.FallbackColor.DWMSBT_NONE);Window.ContentChangedis also raised for secondary-ALC content.Differences from the master PR
fix(win32): Show system backdrops on the default rendererandfix(win32): Keep Vulkan under a system backdrop. The second exactly reverts the first, and both target master's graphics-context negotiation, which 6.7 doesn't have (Win32 defaults to OpenGL here).BorderHelper.UpdateBackground: there's no high-contrastUseBackgroundOverridebranch, because that feature is master-only.CoreServices.NotifyThemeChangeswitched from the plain White/Black helper toFrameworkTheming.GetRootVisualBackground(), which master already used, so there's one source here too. The two are identical outside high contrast.#if UNO_HAS_BORDER_VISUAL, since 6.7 still buildsUno.UIfor Reference and the native renderers.SystemBackdropfeature page instead.Related
On macOS (Metal) and the other
RetainedLayerheads, the ghosting reported in #24495 is a separate blend-mode bug in the damage-layer blit, fixed by #24546. Win32's OpenGL renderer has its own inline copy of that blit, which that PR doesn't yet cover.Validation
Uno.UI.Runtime.Skia.Win32,Uno.UI.Runtime.Skia.MacOSandUno.UI.UnitTests, which includes the Reference flavor. All pass.Given_Window_SystemBackdroppasses 3/3, andGiven_Window1 passed, 1 skipped (pre-existing skip).PR Checklist ✅
Screenshots Compare Test Runresults.Behavior change
Content backgrounds are no longer made transparent when a
SystemBackdropis set. An app that relied on the framework doing this will now see its own opaque backgrounds hide the material. The fix is the one-line change WinUI has always required:This is documented in
doc/articles/features/system-backdrop.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01XU3kKG9jHwupx4TefeXQAe