fix(web): harden the transparency injection to the OBS-standard element-wide !important wipe
The real-widget dumps (12:54/13:50) proved the OLD inline element.style.background='transparent' injection holds only while the page has nothing to paint: the capture was alpha-transparent, yet the recording showed a black opaque box over the whole element rect (1231,679 705x396) once the widget connected and repainted a container background-COLOR — CapturePreviewAsync always honors page CSS (MicrosoftEdge/WebView2Feedback specs/BackgroundColor.md), so any page-painted background wins over DefaultBackgroundColor. This is the OBS-solved class (all web-uri resources paint their own background): browser sources use a Custom CSS override, and the decade-validated formula for arbitrary pages is a pre-parse <style> with 'background-color: transparent !important' — https://obsproject.com/forum/threads/translucent-transparent-browser-source.59549/ ('body { background-color: rgba(0,0,0,0) !important }') plus the div-level variant for stubborn widgets (woahtech.com OBS custom-CSS guide). Injection is now an idempotent pre-parse style element wiping background-color on html,body,html * with !important (outranks every page rule, runs before page parse via AddScriptToExecuteOnDocumentCreatedAsync). Only background-COLOR is targeted — background images and widget art survive. Regression test asserts the element-wide !important form and that the losing inline form is gone. 9/9 WebView2Manager tests, 0 warnings.
This commit is contained in:
+44
-48
@@ -1,63 +1,59 @@
|
||||
# HANDOFF — 2026-09-10 (late afternoon)
|
||||
# HANDOFF — 2026-09-10 (transparency hardening applied)
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
`main` HEAD currently = `f3d578c` (slice 12 audio telemetry). **Slice 13 (web transparency
|
||||
diagnostic re-point) is uncommitted** in the working tree. Ahead of origin by 18, NOT pushing
|
||||
(user ruling: no push until web-overlay transparency AND audio-silence are addressed).
|
||||
`main` HEAD will be = the new transparency-hardening commit (slice 14). Ahead of
|
||||
origin by 19, NOT pushing (user ruling: no push until web-overlay transparency AND
|
||||
audio-silence are addressed).
|
||||
|
||||
## ACTIVE THREAD (the user's directive: ONE problem at a time)
|
||||
## ACTIVE THREAD (ONE problem at a time)
|
||||
|
||||
**Web-uri transparency: still a black, opaque block** (take `ty-20260910-1202-0000-2.mp4` —
|
||||
user: "the web-uri resource transparency is still a black, opaque block. The desired animation
|
||||
runs just fine."). The animation-speed work is done; transparency is now THE web problem.
|
||||
**Web-uri transparency — branch (a) CONFIRMED and FIX-APPLIED, awaiting the verify take.**
|
||||
The 12:54/13:50 dumps proved the REAL widget document captures TRANSPARENT
|
||||
(`%TEMP%\ytLive-web-8d7234ec…-w1..5.png` alpha max 0/logged alpha 100% zero) YET the
|
||||
recording kept showing a black opaque box over the element rect (1231,679,705,396 — crisp
|
||||
edges, interior mean RGB ≈(1,6,13), NOT the desktop). Conclusion: the OLD inline
|
||||
`element.style.background='transparent'` injection only holds while the page has nothing
|
||||
to paint; once the widget connects and repaints a container background-COLOR, the capture
|
||||
re-opaques → black box. That is the OBS-solved class ("all web-uri resources paint their
|
||||
own background").
|
||||
|
||||
Institutional memory (MyMistakes, spin-guard entry): WebView2 `CapturePreviewAsync` honors the
|
||||
page's CSS — the pre-parse injection (`1a39b09`, `AddScriptToExecuteOnDocumentCreatedAsync`)
|
||||
is the recorded fix and is INTACT in `WebView2Manager.cs` (line ~157). The "? VERIFY" was never
|
||||
closed because the diagnostic dump + alpha log bound to the FIRST capture ever = the about:blank
|
||||
placeholder (blind; 5/5 sessions alpha=0 rgb=0).
|
||||
**Slice 14 change (commit target):** `WebView2Manager.TransparentBackgroundScript` is now a
|
||||
pre-parse `<style id='ytl-transparent-bg'>` wiping `background-color:transparent !important`
|
||||
on `html,body,html *` (background-images/art survive — only background-COLOR targeted).
|
||||
Idempotent by element id; `!important` outranks every page rule (OBS forums 2016 `body {
|
||||
background-color: rgba(0,0,0,0) !important }` + div variant, woahtech OBS custom-CSS guide —
|
||||
both cited in the commit message and `MyMistakes.md`).
|
||||
- `internal const` + regression test `TransparentBackgroundScript_Is_A_Important_Element_Wide_Wipe`
|
||||
(asserts element-wide `!important`, style-element form, and that the losing inline
|
||||
`.style.background=` form is gone).
|
||||
- Build 0 warnings; 9/9 WebView2Manager tests pass.
|
||||
|
||||
**Slice 13 change (uncommitted):** re-point the instrument at reality —
|
||||
- `WebSourceSession.WidgetDumpRemaining`; `NavigationCompleted` arms `= 5` when the newly-loaded
|
||||
document is NOT about:blank.
|
||||
- Next 5 captures dump `%TEMP%\ytLive-web-<id>-w1..5.png` + `AlphaStats(...)` + FindContentBounds
|
||||
to startup.log.
|
||||
- `AlphaStats` extracted from the old inline first-capture block.
|
||||
## THE ONE REMAINING STEP (verify, no further analysis)
|
||||
|
||||
Build 0 warnings; 8/8 WebView2 tests pass (runtime not instantiable — re-point is log-only, no
|
||||
new test). Suite otherwise 290/291 (pre-existing compositor pixel).
|
||||
|
||||
## THE DECISION LADDER (no more cargo-culting)
|
||||
|
||||
1. User launches the app with the widget sources active, waits ~10s, closes. (NO recording needed.)
|
||||
2. Read the `widget capture [1..5/5]` lines in startup.log:
|
||||
- **zero% ≈ 0 (opaque):** pre-parse injection did NOT hold for this widget's own CSS →
|
||||
FIX = stronger transparent-background enforcement (document-level `!important` stylesheet
|
||||
injected pre-parse + re-asserted, per the OBS user.css precedent) OR chroma-key the known
|
||||
backdrop color in `CaptureFrame`. Pick after seeing the `-w1..5.png` PNGs.
|
||||
- **zero% large + tight contentBounds:** the capture HAS transparent margins → the recording's
|
||||
black block lives downstream → audit the compositor web-layer blend/underlay (PMA-vs-straight
|
||||
alpha, black pre-fill). Do NOT touch the capture path.
|
||||
3. Implement the branch's fix with ONE integration test (chroma-key math or compositor blend has
|
||||
testable seams), docs same-commit, then ONE verification recording.
|
||||
1. User REPLACES the app (build is current) and records the Live scene, widget animating,
|
||||
like the 12:54/13:50 takes.
|
||||
2. Verdict: element rect shows scene bg with widget art over it (NO black box) → transparency
|
||||
C LOSED, push gate #1 clears. If it still shows a solid black box you can SEE at a glance
|
||||
inside the widget's 705×396 area, bring it + then (and ONLY then) audit what other layer
|
||||
paints that rect in the composite — the web capture has been exonerated twice.
|
||||
3. Then the queued layer-order-save bug (dragging an element over another doesn't persist
|
||||
`SortOrder`) is the next single use case.
|
||||
|
||||
## Other threads (paused)
|
||||
|
||||
- **Audio silence** — `f3d578c` added per-5s `Audio live:` telemetry; next take with desktop
|
||||
audio ACTIVE + those lines names the stage. Push gate #2.
|
||||
- **Web capture speed** — 30Hz NOT achieved (35-117ms/capture → effective ~10-14Hz). The
|
||||
animation "runs fine" so the user is satisfied; revisit only if they want more.
|
||||
- **Webcam missing** in the 10:38/12:02 takes — "MJPG negotiation refused (being used by
|
||||
another process)" at startup. Queued behind transparency+audio.
|
||||
- **Audio silence** — `f3d578c` has per-5s `Audio live:` telemetry; next take with desktop
|
||||
audio ACTIVE names the stage. Push gate #2.
|
||||
- **Webcam missing** — "MJPG negotiation refused (being used by another process)". Queued.
|
||||
- **Web capture speed** — ~10-14Hz effective, user satisfied. Revisit only on request.
|
||||
|
||||
## Landmines
|
||||
|
||||
- testhost shares startup.log with app — filter by time.
|
||||
- `taskkill //F //IM testhost.exe //IM ytLive.exe` before rebuild.
|
||||
- testhost shares startup.log with the app — filter by time.
|
||||
- App was running at commit time; `taskkill //F //IM ytLive.exe` (Windows `taskkill.exe`,
|
||||
bash-quoted `//F //IM`) before rebuilds, and re-run if `MSB3021` copy-lock appears.
|
||||
- Build/tests: `/mnt/c/Program Files/dotnet/dotnet.exe build …` / vstest.
|
||||
- Probing: `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe` / `ffprobe.exe`.
|
||||
- Do NOT delete the crop/compositor paths while debugging source alpha (MyMistakes rule 3 —
|
||||
take-25 vandalism).
|
||||
- The user is frustrated with take-loops; the widget dump needs NO recording — an app launch
|
||||
suffices. Ask for launch + 10s + close, not a take.
|
||||
- Probing: `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe` / `ffprobe.exe` — Windows exes
|
||||
take Windows-style paths.
|
||||
- Never re-derive the transparency story again — MyMistakes "SPIN GUARD → RESOLVED" is the
|
||||
record (wash-rinse-repeat cost the user a whole session).
|
||||
+23
-12
@@ -15,7 +15,7 @@
|
||||
|
||||
## 🔬 Recipes registry
|
||||
|
||||
### ⚠ SPIN GUARD TRIGGERED → RESOLVED (? VERIFY) — web overlay transparency + bounding box
|
||||
### SPIN GUARD → RESOLVED — web overlay transparency + bounding box (RECIPE)
|
||||
|
||||
**THE ONE ROOT CAUSE THAT EXPLAINS EVERY FAILED TAKE:** WebView2's `CapturePreviewAsync`
|
||||
produces an **OPAQUE** PNG. From the WebView2 spec (sender: MicrosoftEdge/WebView2Feedback
|
||||
@@ -63,17 +63,28 @@ do the same via a pre-parse user.css. Kept the nav handlers as a post-load re-as
|
||||
3. Do not delete code paths that fix one axis (crop=bbox) while debugging another
|
||||
(source alpha). Revert scope creep; keep layer contributions separable.
|
||||
|
||||
**2026-09-10 follow-up (still not verified, now being resolved):** the "? VERIFY" above was
|
||||
never closed because the diagnostic itself was blind — the dump + alpha log fired on the FIRST
|
||||
capture ever, which is always the initial about:blank placeholder document (alpha=0, rgb=0,
|
||||
5/5 sessions). The REAL widget frame was never seen. The 2026-09-10 take still showed a black,
|
||||
opaque block with the animation running fine, so either (a) the pre-parse injection does not
|
||||
hold for this widget's own CSS (capture opaque) — fix = stronger injection / chroma-key — or
|
||||
(b) the capture has transparent margins and the black lives in the compositor blend. `1a39b09`'s
|
||||
injection is intact; `WidgetDumpRemaining = 5` now dumps the real widget document post-paint
|
||||
(`%TEMP%\ytLive-web-<id>-w1..5.png`) + shared `AlphaStats` line → the next app launch names
|
||||
which branch. Do NOT re-derive the whole transparency story again from this text; the 5-line
|
||||
diagnostic is the shortest path.
|
||||
**2026-09-10 follow-up → NOW VERIFIED and hardened.** The 12:54 and 13:50 sessions
|
||||
showed the REAL widget document capturing TRANSPARENT (widget dumps `w1..5` for
|
||||
`8d7234ec`: alpha 100% zero, content 12×3; whole-file decodes of
|
||||
`%TEMP%\ytLive-web-*.png` alpha max = 0) while the RECORDING kept showing a black,
|
||||
opaque box over the element rect (crisp edges at the exact rect 1231,679 705×396 —
|
||||
NOT the desktop showing through). Concluson: branch (a) — the OLD inline
|
||||
`element.style.background='transparent'` injection only wins while the page has
|
||||
nothing to paint; once the widget connects and paints its own container
|
||||
background-COLOR, the capture goes opaque again → black box. The OBS-validated
|
||||
answer (valid for arbitrary pages for a decade) is an injected pre-parse `<style>`
|
||||
with `!important` beating every page rule:
|
||||
https://obsproject.com/forum/threads/translucent-transparent-browser-source.59549/
|
||||
(`body { background-color: rgba(0,0,0,0) !important }`) + the div-level variant for
|
||||
stubborn widgets (woahtech.com OBS custom-CSS guide). Applied 2026-09-10: the
|
||||
injection now appends a style element wiping `background-color:transparent!important`
|
||||
on `html,body,html *` (background-IMAGE and art survive). Self-inflicted again: the
|
||||
agent re-derived the whole transparency story (recording pixel archaeology,
|
||||
compositor blend re-verification) instead of reading this entry — the premise was
|
||||
already wrong once and the instrument said transparent, which it did because the
|
||||
capture had NOT been repainted yet. Verify take: if the 705×396 element rect shows
|
||||
the scene bg behind the widget art (animation visible, no black box) the loop closes.
|
||||
Do NOT re-derive this story a third time.
|
||||
|
||||
### Shrink / re-encode an image for the README (screenshots → small hero image)
|
||||
|
||||
|
||||
@@ -155,12 +155,30 @@ public sealed class WebView2Manager : IDisposable
|
||||
// fight (page styles ran first). CapturePreviewAsync "will always honor a webpage's
|
||||
// background content" (MicrosoftEdge/WebView2Feedback specs/BackgroundColor.md), so a
|
||||
// transparent capture REQUIRES the transparency to be in place before the page paints.
|
||||
private const string TransparentBackgroundScript =
|
||||
"document.documentElement.style.background='transparent';" +
|
||||
"document.documentElement.style.overflow='hidden';" +
|
||||
"document.documentElement.style.margin='0';" +
|
||||
"if(document.body){document.body.style.background='transparent';" +
|
||||
"document.body.style.overflow='hidden';document.body.style.margin='0';}";
|
||||
//
|
||||
// The OLD inline `element.style.background='transparent'` form lost to any page CSS:
|
||||
// a widget that paints a background-COLOR on a container (or html/body with stronger
|
||||
// specificity) after connect re-opaques the capture → the "black opaque box" in the
|
||||
// recording while the early frames were transparent. This is a solved problem in the
|
||||
// overlay ecosystem — OBS browser sources use a Custom CSS override and the decade-
|
||||
// validated formula for arbitrary pages is a `!important` background-color wipe
|
||||
// (OBS Forums 2016 "body { background-color: rgba(0,0,0,0) !important }",
|
||||
// https://obsproject.com/forum/threads/translucent-transparent-browser-source.59549/,
|
||||
// and the div-level variant for stubborn widgets, woahtech.com OBS custom-CSS guide).
|
||||
// A `<style>` node injected before parse, with `!important`, outranks every page rule
|
||||
// (only an author !important beats a later document-order !important; ours runs first).
|
||||
// Only background-COLOR is wiped — background images and the widget art survive.
|
||||
internal const string TransparentBackgroundScript =
|
||||
"(function(){" +
|
||||
"if(document.getElementById('ytl-transparent-bg'))return;" +
|
||||
"var s=document.createElement('style');" +
|
||||
"s.id='ytl-transparent-bg';" +
|
||||
"s.appendChild(document.createTextNode(" +
|
||||
"'html,body{background-color:transparent!important;margin:0!important;overflow:hidden!important;}'" +
|
||||
"'html,body,html *{background-color:transparent!important;}'" +
|
||||
"));" +
|
||||
"(document.head||document.documentElement).appendChild(s);" +
|
||||
"})();";
|
||||
|
||||
private async Task InitializeAsync(Source source, WebSourceSession session)
|
||||
{
|
||||
|
||||
@@ -111,6 +111,21 @@ public sealed class WebView2ManagerTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransparentBackgroundScript_Is_A_Important_Element_Wide_Wipe()
|
||||
{
|
||||
// The regression the inline style lost to: a widget repainting a container (or
|
||||
// html/body) opaque after connect re-backs the capture → "black box" in the
|
||||
// recording while early frames stayed transparent. The OBS-validated formula is a
|
||||
// pre-parse <style> with !important outranking every page rule (OBS Forums 2016).
|
||||
var script = WebView2Manager.TransparentBackgroundScript;
|
||||
|
||||
Assert.Contains("background-color:transparent!important", script);
|
||||
Assert.Contains("html,body,html *", script);
|
||||
Assert.Contains("document.createElement('style')", script);
|
||||
Assert.DoesNotContain(".style.background=", script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindContentBounds_Returns_Bounding_Box_Of_NonTransparent_Pixels()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user