fix(web): web-layer capture cadence 10Hz → ~30Hz while recording — widget animations no longer play ~1/6 speed

The recording is 60fps but WebView2 capture was a blind 100ms DispatcherTimer = 10Hz;
each captured web frame repeated ~6x into the file caps web animation at the capture
rate, not the page's (user: 'the animation appears to be too slow').

- New CaptureScheduler (Services/CaptureScheduler.cs): per-session dispatcher timer
  that DROPS a tick while a capture is in flight (latest-wins, never queues) — the
  guard that makes a higher cadence safe: concurrent full-HD PNG CapturePreviewAsync
  calls (~10-30ms each, slow per WebView2Feedback#20) would stack CPU and publish
  stale-after-fresh. Effective cadence = max(interval, capture duration).
- Cadence: SetCaptureInterval(33) on record/stream start, (200) idle — applied via
  MainViewModel.Streaming.Operations.cs.
- De-throttle the hidden page: shared CoreWebView2Environment created BEFORE
  EnsureCoreWebView2Async with --disable-backgrounding-occluded-windows
  --disable-renderer-backgrounding --disable-features=CalculateNativeWinOcclusion.
  Off-screen WebView2 is a hidden page when the host window is unfocused/covered and
  Chromium then parks rAF and clamps timers to ~1s (WebView2Feedback#1172/#3070,
  Chrome-88 timer-throttling blog).
- Telemetry: first 30 captures per session log elapsed ms (PNG encode + decode) to
  startup.log — that decides whether ~30Hz stays or drops to ~20Hz; the FramePump
  drops frames (never time-lapses, slice 10) if UI-thread GC churn starves it.
- ONE test: CaptureScheduler_Drops_Ticks_While_Capture_InFlight_And_Resumes
  (deterministic TCS-driven, no WebView2 runtime). Suite 290/291 — sole failure the
  pre-existing compositor pixel test.
- Docs same-commit: ai.md slice 11, MyMistakes.md, HANDOFF.

References: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1172
https://github.com/MicrosoftEdge/WebView2Feedback/issues/3070
https://github.com/MicrosoftEdge/WebView2Feedback/issues/20
https://developer.chrome.com/blog/timer-throttling-in-chrome-88
This commit is contained in:
2026-09-10 10:25:41 -07:00
parent ec7c734bdd
commit 5e78065c2d
7 changed files with 292 additions and 66 deletions
+28
View File
@@ -154,4 +154,32 @@ public sealed class WebView2ManagerTests
Assert.Equal((0, 0, w, h), (x, y, bw, bh));
}
[Fact]
public async Task CaptureScheduler_Drops_Ticks_While_Capture_InFlight_And_Resumes()
{
// The ~30Hz recording cadence would stack concurrent CapturePreviewAsync calls
// (full-HD PNG encode is ~10-30ms) without the latest-wins drop: ticks that
// arrive while a capture is in flight must be skipped, never queued, and a
// capture can always start once the previous one has finished.
var calls = 0;
var signal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var scheduler = new CaptureScheduler(Dispatcher.CurrentDispatcher, 200, async () =>
{
calls++;
await signal.Task;
});
scheduler.Tick();
scheduler.Tick();
scheduler.Tick();
Assert.Equal(1, calls); // overlapping ticks dropped, never queued
signal.SetResult();
await scheduler.Pending; // durable: Pending completes only after the drop resets
scheduler.Tick();
Assert.Equal(2, calls); // once idle, the next tick captures again
await scheduler.Pending;
}
}