[Problem/Bug]: [WPF] WebView2CompositionControl: ArgumentException (E_INVALIDARG) in GraphicsItemD3DImage.RecoverAfterDisplayChange is not retried and leaves the control blank

Aperta
#5,792 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
8/100
Tipo di issue
Bug
Chiarezza
Da chiarire
Stato di attività
Attiva
Stack tecnologico
csharp
Ambito
desktop-dev

Direzione di ricerca

The report concerns the closed-source WebView2 WPF SDK, and the repository holds only feedback, so no source is available here. The payload names GraphicsItemD3DImage.RecoverAfterDisplayChange, Direct3DHelper.CreateD3D9Device and the catch blocks that handle only COMException. Done would mean a reproducible trigger or a maintainer-confirmed fix, and the work is for maintainers rather than a first contribution.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug
What happened?

After a display settings change, GraphicsItemD3DImage.RecoverAfterDisplayChange threw an unhandled System.ArgumentException ("Value does not fall within the expected range", HRESULT 0x80070057) from Direct3DHelper.CreateD3D9Device. The built-in retry logic only catches COMException, so no retry was scheduled, the exception reached the WPF Dispatcher, and the control is left in a state where it no longer renders. With a debugger attached the process terminated; without one the app would survive, but (from my reading of the IL below, I have not seen the blank page on screen) the WebView area would stay blank for the rest of the process lifetime.

The app (WPF + BlazorWebView, which uses WebView2CompositionControl) was left running unattended, started from Visual Studio, and the screen was most likely asleep. The exception was raised from a Dispatcher operation posted by OnDisplaySettingsChanged (a SystemEvents.DisplaySettingsChanged notification). I could not confirm the trigger.

Stack trace

System.ArgumentException: Value does not fall within the expected range.
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)
   at Microsoft.Web.WebView2.Wpf.Direct3DHelper.CreateD3D9Device(IntPtr mainwindow)
   at Microsoft.Web.WebView2.Wpf.GraphicsItemD3DImage.RecoverAfterDisplayChange()
   at Microsoft.Web.WebView2.Wpf.GraphicsItemD3DImage.<OnDisplaySettingsChanged>b__48_0()
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   ...
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   ...
   at System.Windows.Application.RunInternal(Window window)

Related issues

Searching this repository for RecoverAfterDisplayChange and CreateD3D9Device returns no results. These issues are in the same area (display changes with WebView2CompositionControl) but report different failures:

  • #5437 (closed): UCEERR_RENDERTHREADFAILURE on monitor unplug; a comment says it was believed fixed in SDK 1.0.4191.47 ("Fixed a WPF crash from changing the display topology in composition-controlled apps").
  • #5774 (open, tracked): UCEERR_RENDERTHREADFAILURE / hang on monitor disconnect, still reproducing with 1.0.4314.35; it also mentions a NullReferenceException in Direct3DHelper.GetSharedHandle called from GraphicsItemD3DImage.RequestRender.
  • #5653, #5636, #5575: other GraphicsItemD3DImage / WebView2CompositionControl failures.

The failure reported here is a different one: a managed ArgumentException from the display-change recovery path itself, which the retry logic does not catch.

Analysis (from the shipped IL; no source available)

  1. OnDisplaySettingsChanging sets _displayChangePending = true and clears the D3DImage back buffer.
  2. OnDisplaySettingsChanged posts a lambda (<OnDisplaySettingsChanged>b__48_0) that calls RecoverAfterDisplayChange() inside try { } catch (COMException) { start a 500 ms DispatcherTimer }. The timer tick (<>c__DisplayClass48_0.<OnDisplaySettingsChanged>b__1) retries up to 20 times and also catches only COMException and ObjectDisposedException.
  3. RecoverAfterDisplayChange clears the back buffer, releases the devices, creates the D3D11 device (succeeds), then calls Direct3DHelper.CreateD3D9Device(hwnd), which runs Direct3DCreate9Ex and IDirect3D9Ex.CreateDevice (adapter 0, HAL, windowed) and passes each result through Marshal.ThrowExceptionForHR. It fails there; from the stack alone I cannot tell which of the two calls returned E_INVALIDARG.
  4. Marshal.ThrowExceptionForHR(0x80070057) throws ArgumentException, not COMException (checked on .NET 10: 0x8876086C D3DERR_INVALIDCALL and 0x887A0005 DXGI_ERROR_DEVICE_REMOVED do throw COMException; 0x8007000E throws OutOfMemoryException). So for E_INVALIDARG neither the first call nor any tick is protected, and the retry timer is never started.
  5. _displayChangePending is cleared in only three places: the end of a successful RecoverAfterDisplayChange, its _hwndSource == null early return, and the 20th timer tick. After the failure above none of them runs, so the flag stays true. While it is true, RequestRender returns immediately (disposing the captured frame), so nothing is drawn any more, and the back buffer was set to null by ClearBackBuffer.
  6. Separately: if the retry timer does run and all 20 attempts fail with COMException, the tick silently clears _displayChangePending and stops the timer, leaving the control with released/missing devices and no notification to the host app.

Suggested fix

  • In both the initial call and the timer tick, catch ArgumentException (or simply Exception) in addition to COMException, so the retry is scheduled.
  • Do not leave _displayChangePending set forever, and do not silently clear it when retries are exhausted: surface a failure the host can handle (an event, or a ProcessFailed-style notification).
  • Consider re-running the recovery when IsFrontBufferAvailableChanged fires or on the next display/session notification, not only from the single DisplaySettingsChanged event.
  • Make RecoverAfterDisplayChange safe to call repeatedly after a partial failure.

Workaround in use

Handle Application.DispatcherUnhandledException: if any stack frame belongs to Microsoft.Web.WebView2.Wpf.GraphicsItemD3DImage or Direct3DHelper, mark the exception handled and call the private GraphicsItemD3DImage.RecoverAfterDisplayChange through reflection from a DispatcherTimer (500 ms, then every 5 s) until it stops throwing. This was only tested with a simulated exception; I have not confirmed that it revives a real dead surface.

Not known

  • The real trigger (display sleep, topology change, session lock, RDP) and a deterministic way to reproduce it.
  • Which of the two ThrowExceptionForHR calls in CreateD3D9Device returned E_INVALIDARG.
  • Whether a successful retry fully restores rendering after this failure.
  • Whether SDK 1.0.4314.35 behaves differently; I have only observed this on 1.0.4258.31.
Importance

Important. My app's user experience is significantly compromised.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

156.0.4314.58 (currently installed; the runtime may have auto-updated since the crash)

SDK Version

1.0.4258.31

Framework

WPF

Operating System

Windows 11

OS Version

Windows 11 Pro 26H2, build 26300.9818

Repro steps

I do not have a deterministic repro. It happened once while the app was left running unattended (started from Visual Studio), and the screen was most likely asleep. I could not confirm which display event triggered it.

Setup: a WPF app showing a WebView2CompositionControl (in my case through BlazorWebView), SDK 1.0.4258.31.

  1. While the control is shown, a display settings change happens (suspected: display sleep or a display being removed; not confirmed).
  2. SystemEvents.DisplaySettingsChanged fires and GraphicsItemD3DImage posts RecoverAfterDisplayChange to the Dispatcher.
  3. Direct3DHelper.CreateD3D9Device fails with E_INVALIDARG (0x80070057), which Marshal.ThrowExceptionForHR turns into ArgumentException.

Expected: the recovery is retried until it succeeds (or a failure is reported to the host app), whatever exception type the HRESULT maps to.

Actual: the ArgumentException is not caught, no retry timer is started, it reaches Application.DispatcherUnhandledException (and terminates the process when a debugger is attached), and _displayChangePending stays true, so RequestRender drops every frame afterwards.

Repros in Edge Browser

No, issue does not reproduce in the corresponding Edge version

Regression

Don't know

Last working version (if regression)

No response

Lingua principale
PowerShell
Stelle
526
Fork
70
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di MicrosoftEdge/WebView2Feedback

Tutte le issue di MicrosoftEdge/WebView2Feedback

Issue simili

Altre issue su Desktop Dev

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.