-
Notifications
You must be signed in to change notification settings - Fork 839
Description
Current behavior
The Win32 (Skia) backend hosts WebView2 via a child HWND using CreateCoreWebView2ControllerAsync. This causes three distinct visual defects:
1. White flash on show/hide/recreate
Toggling WebView2 visibility or recreating the control (e.g. navigating between pages, re-templating) produces a visible white flash. This is especially noticeable against dark-themed content. The flash occurs because the child HWND surface is composited by DWM independently from the parent window — when the HWND is shown, its surface contains the default white background for one or more frames before WebView2's multi-process renderer paints real content.
Setting DefaultBackgroundColor to a dark color before navigation reduces the contrast but does not eliminate the flash — there is still a frame where the HWND surface contains the default background, not the rendered page content.
2. Position flash at screen origin
On WebView2 creation, the HWND briefly appears near the top-left corner of the primary monitor before jumping to its correct position inside the app window. This happens because:
Win32NativeWebView.cscreates the HWND withHWND.Nullas parent andCW_USEDEFAULTfor position — making it a top-level window at the OS-chosen default positionWin32NativeElementHostingExtension.AttachNativeElementreparents it viaSetParent(childHwnd, parentHwnd)on a subsequent frame- Between creation and reparenting, DWM composites the HWND at its initial top-level position for one or more vsync cycles
3. Airspace (z-order violation)
The WebView2 child HWND always renders on top of Skia content, regardless of the XAML visual tree z-order. Skia renders to the window DC via WGL SwapBuffers (DWM Layer 1), while child HWNDs are composited on DWM Layer 3 — which is unconditionally above Layer 1. This means:
- XAML elements that should appear above the WebView2 (popups, tooltips, dropdowns, overlays) are hidden behind it
- The current mitigation (
SetWindowRgninWin32NativeElementHostingExtension) provides coarse rectangular clipping but cannot handle transparency, rounded corners, or smooth edges
Expected behavior
- No white flash when showing, hiding, or recreating WebView2
- No position flash — WebView2 content should appear in-place at the correct position
- XAML elements should be able to render on top of WebView2 according to their z-order in the visual tree
How to reproduce
Minimal Uno app that demonstrates all three issues:
<!-- MainPage.xaml -->
<Page x:Class="WebView2FlickerRepro.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="#1E1E1E">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Spacing="8" Padding="8">
<Button Content="Toggle Visibility" Click="ToggleVisibility_Click"/>
<Button Content="Recreate" Click="Recreate_Click"/>
<TextBlock x:Name="StatusText" Foreground="White" VerticalAlignment="Center"/>
</StackPanel>
<Grid Grid.Row="1">
<!-- WebView2 -->
<WebView2 x:Name="MyWebView"
Source="https://example.com"
DefaultBackgroundColor="#1E1E1E"/>
<!-- This border SHOULD render on top of the WebView2 but won't (airspace) -->
<Border Background="#80FF0000"
Width="200" Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="16">
<TextBlock Text="I should be on top of WebView2"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</Grid>
</Grid>
</Page>// MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
private void ToggleVisibility_Click(object sender, RoutedEventArgs e)
{
// Issue 1: Each toggle produces a white flash
MyWebView.Visibility = MyWebView.Visibility == Visibility.Visible
? Visibility.Collapsed
: Visibility.Visible;
StatusText.Text = $"Visibility: {MyWebView.Visibility}";
}
private void Recreate_Click(object sender, RoutedEventArgs e)
{
// Issue 1 + 2: Recreating produces white flash AND position flash at screen origin
var parent = (Grid)MyWebView.Parent;
var source = MyWebView.Source;
parent.Children.Remove(MyWebView);
var newWebView = new WebView2
{
Source = source,
DefaultBackgroundColor = Windows.UI.Color.FromArgb(255, 30, 30, 30)
};
Grid.SetRow(newWebView, 0);
parent.Children.Insert(0, newWebView);
MyWebView = newWebView;
StatusText.Text = "Recreated";
}
}Steps to reproduce:
- Create a new Uno app targeting
net10.0-desktop(Win32/Skia backend) - Add the XAML and code-behind above
- Run on Windows 11
- Issue 1: Click "Toggle Visibility" rapidly — observe white flash on each toggle
- Issue 2: Click "Recreate" — observe the WebView2 HWND briefly flash near the top-left of the monitor before appearing in the correct position
- Issue 3: Observe the red semi-transparent overlay — it is hidden behind the WebView2 instead of rendering on top
Root cause analysis
All three issues stem from hosting WebView2 via CreateCoreWebView2ControllerAsync (windowed/HWND mode). This is a fundamental limitation of the Win32 HWND composition model.
DWM layer model
Windows Desktop Window Manager composites content in four layers:
Layer 4: DirectComposition topmost visuals
Layer 3: Child HWNDs ← WebView2 lives here (always on top of parent)
Layer 2: DirectComposition non-topmost visuals
Layer 1: GDI / OpenGL window DC ← Skia renders here (WGL SwapBuffers)
Flickering: When a child HWND is shown or created, DWM composites its surface immediately — but the surface may not yet contain rendered content (WebView2 renders in a separate process). The gap between "surface visible" and "content painted" is the flash.
Position flash: The HWND is created as a top-level window (parent: HWND.Null) then reparented. DWM composites it at the initial position for at least one frame before SetParent moves it.
Airspace: Layer 3 is unconditionally above Layer 1. No amount of z-ordering in the XAML tree can make Skia content (Layer 1) render above a child HWND (Layer 3).
Relevant source files
| File | Role |
|---|---|
Win32NativeWebView.cs |
Creates child HWND with HWND.Null parent, calls CreateCoreWebView2ControllerAsync (windowed mode) |
Win32NativeElementHostingExtension.cs |
Reparents via SetParent, clips via SetWindowRgn |
Win32WindowWrapper.Rendering.OpenGl.cs |
Skia renders to window DC via WGL SwapBuffers (Layer 1) |
RenderSurfaceType.cs |
Renderer enum: only Software and OpenGL — no DirectComposition option |
Why this cannot be fixed within the HWND model
The parent process and the browser process (WebView2) paint independently, and DWM composites them independently. There is no Win32 synchronization primitive that atomically makes an HWND visible with content already painted. Workarounds (delayed visibility, DefaultBackgroundColor, WS_EX_COMPOSITED) reduce severity but cannot eliminate the flash because:
DefaultBackgroundColoronly controls the pre-navigation background — the HWND surface is still composited before the browser process paintsWS_EX_COMPOSITEDenables GDI double-buffering, which doesn't apply to WGL or WebView2's multi-process rendererBeginDeferWindowPos/EndDeferWindowPosbatch position changes but don't control paint timingWM_SETREDRAWsuppresses repaints temporarily but re-enabling it triggers the same flash
Proposed solution: DirectComposition rendering mode
WinUI does not have these issues because it uses CreateCoreWebView2CompositionControllerAsync instead of CreateCoreWebView2ControllerAsync. The WebView2 renders into a DirectComposition visual — not a child HWND.
The full fix would add a DirectComposition renderer to the Win32 backend where both Skia and WebView2 render into the same DComp visual tree:
Win32 Window (WS_EX_NOREDIRECTIONBITMAP)
└── DComp Target
├── Skia Visual: SkiaSharp → ANGLE (OpenGL ES → D3D11) → IDCompositionSurface
└── WebView2 Visual: CoreWebView2CompositionController → RootVisualTarget
- No child HWND — eliminates position flash and HWND lifecycle flash
- Atomic composition — DComp commits are atomic; content appears only when ready
- True z-ordering — Skia and WebView2 are sibling DComp visuals, z-ordered by tree position
- No airspace problem — XAML elements can render on top of WebView2
Scope of changes (Win32 backend only)
| Change | File | Description |
|---|---|---|
| Modify | RenderSurfaceType.cs |
Add DirectComposition enum value |
| Modify | Win32WindowWrapper.cs |
Use WS_EX_NOREDIRECTIONBITMAP when DComp renderer is active |
| Modify | Win32NativeWebView.cs |
Use CreateCoreWebView2CompositionControllerAsync + input forwarding via SendMouseInput |
| Modify | Win32NativeElementHostingExtension.cs |
DComp path: create IDCompositionVisual instead of child HWND |
| New | Win32WindowWrapper.Rendering.DirectComposition.cs |
DComp renderer: D3D11 → ANGLE → Skia into IDCompositionVirtualSurface |
| New | AngleEglBridge.cs |
ANGLE EGL display/context management for D3D11↔OpenGL ES bridge |
| New | DCompInterop.cs |
COM interfaces for IDCompositionDevice, IDCompositionTarget, IDCompositionVisual |
| Dependency | ANGLE native binaries | libEGL.dll + libGLESv2.dll (~10MB) |
No other platforms (WASM, macOS, Linux, iOS, Android) are affected. The existing Software and OpenGL renderers remain unchanged.
Prior art
- WinUI: Uses composition mode natively for WebView2 (
WebView2.cpp→CreateCoreObjects()) - Avalonia: Renders Skia into DComp surfaces via ANGLE (
DirectCompositedWindowSurface.cs,AngleWin32EglDisplay.cs) - Wice framework: .NET implementation of DComp + WebView2 composition (
WebView.cs) - WebView2 API Sample: DComp visual tree setup (
ViewComponent.cpp)
Immediate partial fix (lower effort)
A smaller change that fixes flickering but NOT airspace: use CreateCoreWebView2CompositionControllerAsync with a DComp overlay, while Skia continues rendering via WGL. This avoids the ANGLE dependency entirely (~250-400 new lines, zero new binary dependencies) but WebView2 would still always render on top of Skia content (Layer 2 above Layer 1).
Environment
- Uno.WinUI: 5.x / 6.x (Win32/Skia backend)
- .NET: 9 / 10
- OS: Windows 10/11
- WebView2 Runtime: Evergreen
Related issues
- [WebView2] [Desktop/Wasm]
WebView2.Visibilityis not working as expected #21376 — WebView2 Visibility not working as expected (z-order / airspace symptom) - WebView2 control steals focus from window and prevents bringing it to the front #22641 — WebView2 control steals focus from window