Files
LlamaCasty/TASKS/task-17-web-source-rendering.md
T
gramps c01206fb8a feat: web sources frame-captured via composition (CoreWebView2CompositionController → Windows.Graphics.Capture); PNG poll + CaptureScheduler deleted
The ~30Hz CapturePreviewAsync PNG poll capped real cadence at ~20Hz
(35–165ms full-HD encode+decode), so a 60fps widget still juddered at ~1/6
speed. Replaced polling with frame-driven capture of the composition
controller's root visual — the mechanism WebView2CompositionControl and
Flutter's webview_windows use (graphics_context.cc captures the root
surface_ visual via CreateGraphicsCaptureItemFromVisual; reference:
github.com/microsoft/Windows.UI.Composition.WinUI / flutter-internal
webview_windows). Frames now arrive at the renderer's own pace; capture
memory is epoch'd ring reuse + one crop-sized shared WriteableBitmap.

New Services/WebCaptureFrameSource.cs owns GraphicsCaptureItem + free-
threaded Direct3D11CaptureFramePool + session (Straight alpha readback,
per-frame FindContentBounds → CropBounds). WebView2Manager reworked around
per-session composition controllers + one UI-thread Compositor created via
the CoreMessaging CreateDispatcherQueueController P/Invoke (the 19041
projection lacks CreateOnCurrentThread); internal seam ctor
(Dispatcher, Func<string,IScreenCaptureSource>?) for hermetic tests.
CaptureScheduler.cs deleted; the three SetCaptureInterval cadence hooks
removed; InitWebView2() moved from MainWindow ctor to Loaded (a parent
HWND must exist for the composition controller); the hidden WebViewHostPanel
overlay deleted. TransparentBackgroundScript unchanged.

Tests: WebView2ManagerTests reworked — 4 control-size + scheduler tests
dropped, FindContentBounds tests moved to WebCaptureFrameSource, ONE
integration test (Frames_PublishCroppedPreview_And_CoalesceToLatest_CarryingCropBounds)
drives the seam with a FakeWebSource + background-STA DispatcherPump.
Suite 293/293, 0 warnings.

NOTE: composition path NOT yet verified on a device — the take is the
next step. Web work committed locally only (no push per standing rule).

Docs same-commit: ai.md Slice 14 + supersede marker on Slice 11, HANDOFF,
MyMistakes (CoreMessaging DQ + namespace-landmine recipe), TASK 17,
Controls/ViewModels/Services indexes.
2026-09-14 16:14:00 -07:00

64 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK 17 — Web source rendering (WebView2)
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
**Goal:** make the web source actually render URLs into the preview and stream output.
### Status: ✅ Done — required for v1 (2026-08-28)
1. ✅ Add `Microsoft.Web.WebView2` NuGet package
2. ✅ Schema v10: `WebUri TEXT` column on `Source` table + migration in `LayoutStore.cs`
3. ✅ Persist `Source.WebUri` on save/load (currently in-memory only — lost on restart)
4. ✅ Hidden off-screen `WebView2` control per web source — navigates to `WebUri`, renders in-app
5. ✅ Frame capture from WebView2 (**superseded 2026-09-14:** was `CoreWebView2.CapturePreviewAsync`
→ `WriteableBitmap` (BGRA8); now composition-capture via
`CoreWebView2CompositionController` → `Windows.Graphics.Capture` — see the rendering-model
update below. PNG polling is deleted)
6. ✅ Wire into `FramePump` resolver — `Source { Type: WebSource }` → latest WebView2 frame
7. ✅ Wire into `SceneCompositor` — render web source as an image element at its position/size
8. ✅ Preview shows live web content (not just a blank rectangle)
9. ☐ Handle navigation errors, invalid URIs, timeout gracefully
**Rendering model (2026-08-28, canvas-size viewport + ALPHA-BBOX crop — DONE, creator-verified):**
the page renders at the MASTER CANVAS size (1920×1080), stable, never tracked (no reflow/truncation;
scrollbars suppressed via `overflow:hidden`). `FindContentBounds` scans the Bgra32 capture and crops
to the bounding box of non-transparent pixels — the widget's true rendered extent, measured from the
frame itself (NO DOM query, immune to layout timing; can never truncate content; full-canvas widgets
fall through to the full frame = prior verified-good image). `Stretch="Fill"` maps the cropped frame
flush under the selection box → box hugs the widget on all four sides. `QueryContentBoundsAsync` +
the JS content-bounds script are DELETED. **RESULT: bounding verified PERFECT by the creator with
two widgets.** Root-cause note on the earlier "remaining defect/gap": that was a WIDGET GLOW EFFECT
(the widget's own CSS glow pushes out its perceived borders), not a code bug — the alpha-bbox was
already hugging the glow halo. Lesson: test with a second, plain widget before changing code.
⚠️ Lesson bank: never return `JSON.stringify` from `ExecuteScriptAsync` (double-encodes); never
measure DOM stuff on NavigationCompleted (unsettled layout broke the image — `a5b9952`); avoid
sizing the container to the crop (`ab29ec8` reverted — made it worse); a widget glow effect can
fake a gap — verify with a plain widget first; measure rendered pixels instead.
**Rendering model update (2026-09-14, COMPOSITION capture — the PNG poll is gone):** frame capture
is no longer `CapturePreviewAsync` → PNG (35–165ms per full-HD encode+decode capped real cadence at
~20Hz; item 5 below is superseded). Each session now renders through a
`CoreWebView2CompositionController` visual into `Windows.Graphics.Capture`
(`GraphicsCaptureItem.CreateFromVisual` of the root 1920×1080 `ContainerVisual`), pulled frame-driven
from a free-threaded `Direct3D11CaptureFramePool` at the renderer's own rate (~60fps, parity with the
browser). Straight-alpha readback, alpha-bbox crop + `CropBounds` per frame, epoch'd ring reuse, one
shared crop-sized preview bitmap. The old `SetCaptureInterval` cadence hooks and `CaptureScheduler`
are deleted; the hidden XAML `WebViewHostPanel` overlay and `CapturePreviewAsync` telemetry are gone.
`TransparentBackgroundScript` is unchanged (a test pins it). Full design + the CoreMessaging DQ
recipe in `../ai.md` → Slice 14. **Open: device verification of ~60fps web animation.**
**Properties panel (2026-08-28):** web URI ✓/✕ icon buttons are `IsTabStop="False"` so Tab flows
X→Y→W→H→URI; the ✕ button now clears the URI textbox (was reverting to the pre-accept snapshot).
Slider style gained `IsMoveToPointEnabled="True"` — click-anywhere-on-bar jumps the thumb to the
click (volume sliders keep their manual `SetSliderValueFromClick`, harmless duplication).
### Design decisions
- WebView2 is the only option for Windows — it's pre-installed on Windows 10 20H2+ and Windows 11
- The web source is a standard element — positioned/sized/opacitied like any image source
- Frame capture is drive-by-renderer (composition capture, ~60fps parity with the browser since
2026-09-14; the old "5-10 fps is fine" poll model is deleted)
- This enables Streamlabs/StreamElements overlays via web URLs