Canvas raster fallback, CDP rest-state screenshots, lottie overlay containment
- Visible canvases (>=48px) are captured as PNG stills at capture time (toDataURL, element-screenshot fallback for tainted/webgl) and emitted as cid-carrying <img> elements filling the captured box, so canvas-driven bands no longer render blank - Source AND clone full-page screenshots now capture via CDP Page.captureScreenshot with captureBeyondViewport: no scroll-stitching, no scroll events, so scroll-linked animations can't smear mid-scrub states into perceptual evidence (pixel-identical to the old path on static pages; Playwright fallback retained) - DittoLottie's runtime overlay stays position:absolute/inset:0 for its whole life and the host carries data-ditto-lottie with a scoped svg/canvas fit rule, so the runtime animation always fills the pinned per-viewport box instead of escaping to aspect height 259 tests pass (12 new), typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e33354cfb5
commit
15d1e7a9e3
@@ -37,6 +37,33 @@ describe("DittoLottie placeholder retention", () => {
|
||||
assert.match(DITTO_LOTTIE_TSX, /data_failed/);
|
||||
});
|
||||
|
||||
// Sizing: the host box carries the captured per-viewport height; only an absolutely-filled overlay
|
||||
// inherits that definite box. If the reveal reverted the overlay to static flow, the player's svg
|
||||
// (style height:100%) would collapse to auto and inflate to the source aspect (a portrait viewBox
|
||||
// then oversizes far past the captured, letterboxed box). So the overlay must STAY absolute/inset
|
||||
// for its whole life, and the reveal must NOT clear those styles.
|
||||
it("mounts the overlay as an absolute, inset-0 layer", () => {
|
||||
assert.match(DITTO_LOTTIE_TSX, /mount\.style\.position\s*=\s*["']absolute["']/);
|
||||
assert.match(DITTO_LOTTIE_TSX, /mount\.style\.inset\s*=\s*["']0["']/);
|
||||
});
|
||||
|
||||
it("keeps the overlay absolute after the swap (never reverts it to static flow)", () => {
|
||||
// The reveal must not strip the overlay's positioning — a `mount.style.position = ""` (or inset)
|
||||
// un-pins it from the host's captured height and lets the runtime svg inflate by aspect.
|
||||
assert.ok(
|
||||
!/mount\.style\.position\s*=\s*["']["']/.test(DITTO_LOTTIE_TSX),
|
||||
"reveal must not clear mount.style.position (would un-pin the overlay → aspect inflation)",
|
||||
);
|
||||
assert.ok(
|
||||
!/mount\.style\.inset\s*=\s*["']["']/.test(DITTO_LOTTIE_TSX),
|
||||
"reveal must not clear mount.style.inset (would un-pin the overlay → aspect inflation)",
|
||||
);
|
||||
});
|
||||
|
||||
it("marks the host with data-ditto-lottie so emitted CSS can force the runtime svg to fit", () => {
|
||||
assert.match(DITTO_LOTTIE_TSX, /setAttribute\(\s*["']data-ditto-lottie["']/);
|
||||
});
|
||||
|
||||
// The player creates its <svg>/<canvas> at runtime WITHOUT a data-cid, so the DOM/media gate can't
|
||||
// map it back to the captured node. The reveal must forward the discarded placeholder's data-cid
|
||||
// onto the runtime-rendered element (validation-agnostic; no gate special-casing).
|
||||
|
||||
Reference in New Issue
Block a user