⚗️ Add Canvas image capture [3/n] - #4980
Conversation
Bundles Sizes Evolution
|
|
✅ All CI checks and tests passed. 🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 3115990 | Docs | View more details | Give us feedback! |
144fbb9 to
af5efab
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e88d1dda23
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04324e8b22
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc5306758c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1eed2ca243
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d830fc9856
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3b3f5c59b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02f8ab1a5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex pls review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Motivation
Session Replay does not capture the contents of
<canvas>elements, so they show up blank in the replay. Building on #4947 (opt-in configuration) and #4949 (dirty-canvas tracking), this PR turns dirty canvases into PNG images plus a hash that tells us when their content actually changed.Design diagram: View the Mermaid diagram
Changes
New
trackCanvasCapturetracker: every1s / maxFramesPerSecond, for each dirty, connected, non-tainted canvas whose privacy level isallow— take a downscaled snapshot, hash it, drop it if the hash matches the last one emitted for that canvas, otherwise encode a PNG and emit{ nodeId, changeHash, image }. Snapshotting and hashing live incanvas/canvasSnapshot.tsandcanvas/canvasHash.ts, each with its own spec; the tracker only orchestrates. Two new options:maxImageDimension(default 1000) for the recorded image andhashingMaxDimension(default 100) for the change-detection thumbnail.Decisions
One immutable snapshot per capture, reused for hashing and encoding, so the emitted hash always describes the emitted bytes even if a draw lands mid-capture — the next PR uses that hash as a dedup key. A draw during a capture leaves the canvas dirty for the next tick.
The hash is computed on a ~100px thumbnail, so change detection costs the same at any canvas size. The canvas dimensions are part of it, so a resize always counts as a change.
The snapshot is a 2D canvas, not
createImageBitmap. The latter resizes off the main thread, but it fails on 2 of the 5 browsers inbrowsers.conf.ts:(image, options)overload → synchronousTypeErrorcreateImageBitmapat all (Baseline only since Sept 2021, i.e. Safari 15)Neither throw is a
SecurityError, so those canvases would be retried on every tick forever. Supporting them means keeping adrawImagepath anyway, and the numbers say the fast path does not earn a second code path (Chrome 151, macOS, GPU on, medians of 8 runs, ms):createImageBitmapdrawImageImageBitmaptoBlobtoBlobThe snapshot is free either way even from 4K because
drawImagedownscaling is GPU-accelerated, hashing is ~2× faster from a canvas, and the PNG encode dominates at 5–7.5 ms in both. If a machine without GPU acceleration turns that synchronous downscale into a real cost,createImageBitmapcomes back as a feature-detected fast path.CanvasManagergoes from tracking dirty canvases to owning capture state: in-flight capture, last emitted hash and tainted set, all in oneWeakMapbehind 6 methods.capture(canvas, run)owns the in-flight lifecycle and hands out an attempt whosesettle/failare guarded internally, so a caller cannot leak it or act on a capture that is no longer current.Once tainted, always tainted. The platform would allow a retry after a bitmap reset, but that means a failed capture on every resize; the trade-off is that a canvas which stops being tainted stays frozen in the replay until recording restarts.
The attempt is the single staleness signal. Privacy level and node id are read once, at capture start; nothing re-validates the node id mid-flight because it cannot change without invalidating the attempt (
processRemovedNodescallsforgetCanvas,resetIdscallsreset()).crypto.subtlehas a non-crypto fallback: it is secure-context-only, so it is missing on plain HTTP pages in every browser, current Chrome included. No browser matrix can exercise that, so a mocked spec does.Assigning
widthorheightresets the bitmap and drops the last hash, so the next tick re-captures the canvas — including when the value is unchanged, sincecanvas.width = canvas.widthis the standard idiom for clearing a canvas. Hence the check runs before the "no change since the last snapshot" early return inserializeMutations.Scope
This PR adds the capture primitive and callback interface. Wiring captured blobs into Session Replay event serialization and intake delivery is a follow-up step.
Test instructions
Checklist