MicrosoftEdge / MicrosoftEdge/WebView2Feedback
Regression (~2026-07-02 runtime update): embedded WebView2 leaves stale compositor tiles when page-sized img/canvas content swaps at unchanged layout size after zoom — same version renders sharp in Edge tab
Nobody has claimed this yet.
- Dominant language
- PowerShell
- Stars
- 526
- Forks
- 67
- PR merge metrics
- No merged PRs in 30d
Description
What happened?
Around 2026-07-02, an evergreen WebView2 Runtime update regressed re-rasterization of page-sized <img>/<canvas> content in embedded WebView2 (wry/Tauri windowed hosting): after a zoom interaction, swapping the element's content at an unchanged layout size leaves part of the composited output holding the stale, CSS-stretched previous bitmap indefinitely — the on-screen page looks soft/blurry even though the new bitmap is pixel-sharp. The identical HTML, bitmaps, and timing render sharp in an Edge tab on the same machine at the same engine version (149.0.4022.98).
This is a report from a production PDF viewer (Tauri 2 / wry, Windows). We spent a three-week forensic investigation isolating it; the evidence below is unusually complete, including pixel-level tile forensics and a zero-code-change historical-binary regression proof.
Evidence chain (each item independently verified):
- The served bitmaps are sharp. Decoding the app's render-cache blobs from a repro session and measuring edge gradients: the exact
RenderedPagethat displayed soft decodes sharp (identical LCD fringing to the sharp-at-open render). - Partial re-raster, tile-aligned. Registering a screenshot of the soft state against the served bitmap: page rows 0–~440 are pixel-identical to the fresh bitmap (SAD ≈ 0.04); below that the pixels match the pre-zoom bitmap stretched into the new box, easing toward the stretch's phase-realignment row. The clean/dirty boundary lands at screen-y ≈ 512 — a 256-px compositor tile boundary (dpr = 1 machine, so screenshots are framebuffer-exact).
- Trigger choreography: zoom first resizes the element (live CSS scale of the old bitmap — repaints fine), then the app swaps in the freshly rendered bitmap at the now-unchanged layout size. A swap that changes layout size in the same flush (our Ctrl+0) repaints fully and is instantly sharp.
- Element identity does not help. Six display implementations all reproduce it in the embedded app: persistent
<img>with src swap, keyed<img>remount (fresh element per bitmap),<canvas>+drawImage(fresh backing-store write per swap),ImageBitmap,putImageData, and full subtree unmount-per-refetch. Remounting the element or rewriting a canvas backing store does not reliably force a full re-raster of the composited output. - Zero-code-change regression. An app binary built from a commit that was verifiably sharp through this exact zoom protocol on 2026-07-02 (rebuilt with era-exact lockfiles, cache cleared) renders soft on the same machine days later. Nothing in the app changed; the evergreen runtime did.
- Not the GPU driver's raster: launching the embedded app with
--disable-gpu(software compositing) still reproduces. Raster-path flags (--disable-partial-raster,--disable-gpu-rasterization) do not cure it either. - Not reproducible in the Edge browser: self-contained probe pages carrying the app's real bitmaps, transport (PNG data URLs), and exact resize-then-swap timing — single page and 15-page load-pressure variants — render pin-sharp in an Edge tab at the same version, including a "full app simulation" variant (thumbnail column, overlays, rAF churn). The delta is specific to embedded hosting.
Importance
High — the app's core promise (pixel-sharp document rendering at any zoom) could not be met on any evergreen runtime after the regression, on code that had shipped unchanged for weeks. We ultimately moved document pixels off WebView2 entirely (native DirectComposition surface under a transparent webview), which is a heavy remediation no code inside the webview could avoid; filing so the embedded re-raster path gets a look and other embedded document viewers don't hit the same wall.
Runtime Channel
Stable
Runtime Version
149.0.4022.98 (regression window ≈ 2026-06-30 → 2026-07-02; the machine was demonstrably sharp on the same app code on 2026-07-02 before the runtime updated)
SDK Version
WebView2 via wry 0.55.1 / Tauri 2 (windowed hosting, CreateCoreWebView2Controller)
Framework
Win32 (Rust — Tauri 2 / wry embedded host)
Operating System
Windows 11 (desktop; 100% display scaling / dpr 1 — exact OS build available on request)
Repro steps
The probe below re-creates the app's choreography with synthetic high-detail bitmaps. It renders sharp in an Edge tab (that is the point — item 7 above); reproducing the defect requires hosting it embedded (any wry/Tauri/WebView2 sample host; we can supply a minimal wry-based host repro on request, and can share the original probe corpus carrying the app's real page bitmaps).
- Host the HTML below in an embedded WebView2 (windowed controller), window ≥ 1700×1200, on a 100%-scaling display, runtime 149.0.4022.98+.
- Let it cycle. Each cycle: bitmap at rest (100%) → live CSS-scale to ~203% → relayout to the zoomed box → content swap at unchanged layout size (HUD shows "INSPECT NOW").
- Expected: after the swap the whole page is sharp (it is, in an Edge tab).
- Actual (embedded, affected runtime): part of the page — typically below the first few 256-px tile rows — keeps the stretched pre-zoom texture: uniform softness with weakened detail, persisting indefinitely (no later repaint fixes it; scrolling the stale region can).
probe.html (self-contained, no network)
<!DOCTYPE html>
<!-- colophon-render-probe-synthetic.html
Self-contained re-creation of the zoom choreography that triggers the
stale-tile re-raster in embedded WebView2 (sharp in an Edge tab).
Sequence per cycle, mirroring a document viewer's zoom:
1. show bitmap A (1656x2142-class page) at layout size L, 1:1;
2. "zoom": CSS-scale the element live (layout size unchanged);
3. settle: relayout to the new layout size L2 (element box changes);
4. ~150ms later swap the content to freshly rendered bitmap B whose
natural size == L2 exactly (a swap at UNCHANGED layout size).
Step 4 is where the embedded runtime left lower compositor tiles holding
the stale bitmap-A-stretched texture indefinitely. -->
<html><head><meta charset="utf-8"><title>webview2 re-raster probe</title>
<style>
body { margin: 0; background: #d7d4cf; font: 12px monospace; }
#hud { position: fixed; top: 4px; left: 4px; background: #000c; color: #fff; padding: 4px 8px; z-index: 9; }
#page { display: block; margin: 40px auto; box-shadow: 0 3px 16px #0003; }
</style></head><body>
<div id="hud">probe</div>
<img id="page" alt="">
<script>
// A "page" bitmap: dense 1-px detail so any resample is visible — serif-ish
// text lines, a 1px checkerboard band, and hairline rules, rendered at the
// exact device pixel size requested (no DPR tricks; run at 100% scaling).
function renderPage(w, h, label) {
const c = document.createElement('canvas'); c.width = w; c.height = h;
const g = c.getContext('2d');
g.fillStyle = '#fff'; g.fillRect(0, 0, w, h);
g.fillStyle = '#000';
for (let y = 24; y < h - 40; y += 18) { // text lines
g.font = '13px Georgia, serif';
g.fillText(`${label} line ${y} the quick brown fox 0123456789 ijklmn`, 16, y);
}
for (let y = h - 36; y < h - 20; y++) // 1px checkerboard band
for (let x = 16; x < w - 16; x++)
if ((x + y) & 1) g.fillRect(x, y, 1, 1);
g.fillRect(16, h - 12, w - 32, 1); // hairline
return c.toDataURL('image/png');
}
const img = document.getElementById('page'), hud = document.getElementById('hud');
const BASE = { w: 816, h: 1056 }; // 100%
const ZOOMED = { w: 1656, h: 2142 }; // ~203%
let cycle = 0;
function setLayout(el, w, h) { el.style.width = w + 'px'; el.style.height = h + 'px'; }
async function run() {
img.src = renderPage(BASE.w, BASE.h, 'A' + cycle); setLayout(img, BASE.w, BASE.h);
img.style.transform = ''; hud.textContent = `cycle ${cycle}: rest @100%`;
await new Promise(r => setTimeout(r, 1200));
// live "gesture": CSS scale, layout size unchanged (repaints fine)
img.style.transformOrigin = 'top center';
img.style.transform = `scale(${ZOOMED.w / BASE.w})`;
hud.textContent = `cycle ${cycle}: CSS-scale interim`;
await new Promise(r => setTimeout(r, 400));
// settle: real relayout to the zoomed box (still showing bitmap A stretched)
img.style.transform = ''; setLayout(img, ZOOMED.w, ZOOMED.h);
hud.textContent = `cycle ${cycle}: relayout, awaiting fresh bitmap`;
await new Promise(r => setTimeout(r, 150));
// THE TRIGGER: swap to the fresh bitmap at UNCHANGED layout size.
img.src = renderPage(ZOOMED.w, ZOOMED.h, 'B' + cycle);
hud.textContent = `cycle ${cycle}: swapped @203% — INSPECT NOW: whole page must be sharp`;
await new Promise(r => setTimeout(r, 4000));
cycle++; run();
}
run();
</script></body></html>
Repros in Edge Browser
No — identical content/choreography is sharp in an Edge tab at the same engine version. Embedded-only.
Regression
Yes — worked before ≈ 2026-07-02, broken after, with zero application-code change (item 5 above).
AB
Unknown — we did not test controlled feature-flag variations beyond the command-line switches in item 6; happy to run experiments if you can suggest flags.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the embedded WebView2 reproduction using the supplied probe.html and the wry/Tauri host on runtime 149.0.4022.98; compare its output with the same probe in an Edge tab. Done means the stale compositor tiles are either explained and corrected in the embedded path or the regression is narrowed to a confirmed runtime behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics, desktop
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100