Skip to content

⚗️ Capture dirty canvas images - #4951

Draft
BeltranBulbarellaDD wants to merge 1 commit into
beltran.bulbarella/canvas_support-2-prefilterfrom
beltran.bulbarella/canvas_support-3-capture-images
Draft

⚗️ Capture dirty canvas images#4951
BeltranBulbarellaDD wants to merge 1 commit into
beltran.bulbarella/canvas_support-2-prefilterfrom
beltran.bulbarella/canvas_support-3-capture-images

Conversation

@BeltranBulbarellaDD

Copy link
Copy Markdown
Contributor

Motivation

Session Replay canvas support needs to capture changed canvas contents at the configured frame rate without repeatedly processing identical images. This stacked PR adds the image-capture and in-memory frame-deduplication stage after the canvas dirty-state pre-filter introduced in #4949.

Design references:

Changes

  • Keep dirty canvases enumerable until they are captured, and clear retained canvases after capture or recorder shutdown.
  • Capture dirty canvases as PNG blobs at the configured maximum frame rate.
  • Compute a SHA-256 resource identifier from each PNG.
  • Skip consecutive frames whose hash has not changed while preserving transitions such as frame A → frame B → frame A.
  • Prevent overlapping captures and preserve mutations made while a capture is in flight.
  • Expose changed images through the recorder's canvasImageObservable for the resource-upload and replay-record follow-ups.
  • Add focused unit coverage and a visible E2E canvas scenario that verifies real PNG capture, clean-frame filtering, and attribute-resize capture.

Resource upload and canvas-to-resource replay records are intentionally left for follow-up PRs.

Test instructions

yarn test:unit --spec packages/browser-rum/src/domain/record/canvas/canvasCapture.spec.ts --spec packages/browser-rum/src/domain/record/canvas/canvasManager.spec.ts --spec packages/browser-rum/src/domain/record/trackers/trackCanvas.spec.ts --spec packages/browser-rum/src/domain/record/record.spec.ts
yarn typecheck
yarn eslint packages/browser-rum/src/domain/record/canvas/canvasCapture.ts packages/browser-rum/src/domain/record/canvas/canvasCapture.spec.ts packages/browser-rum/src/domain/record/canvas/canvasManager.ts packages/browser-rum/src/domain/record/canvas/canvasManager.spec.ts packages/browser-rum/src/domain/record/record.ts packages/browser-rum/src/domain/record/record.spec.ts test/e2e/scenario/recorder/canvas.scenario.ts
yarn build:apps --app vanilla
yarn test:e2e -g "captures dirty canvases periodically"

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@cit-pr-commenter-54b7da

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 181.20 KiB 181.20 KiB 0 B 0.00%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 22.20 KiB 23.14 KiB +965 B +4.24%
Logs 57.47 KiB 57.47 KiB 0 B 0.00%
Rum Salesforce N/A 139.15 KiB N/A N/A N/A
Rum Slim 139.14 KiB 139.14 KiB 0 B 0.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%
Rum Shopify N/A 202.85 KiB N/A N/A N/A
Rum-shopify Profiler N/A 8.43 KiB N/A N/A N/A
Rum-shopify Recorder N/A 3.72 KiB N/A N/A N/A

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Aug 14, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 13 Pipeline jobs failed

DataDog/browser-sdk | unit   View in Datadog   GitLab

🧪 1 Test failed

record canvas mutation tracking captures dirty canvases at the configured maximum frame rate from Chrome Headless 151.0.0.0 (Linux 0.0.0)   View in Datadog
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    at <Jasmine>

DataDog/browser-sdk | build-and-lint   View in Datadog   GitLab

DataDog/browser-sdk | build-bundle   View in Datadog   GitLab

View all 13 failed jobs.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 73.81%
Overall Coverage: 77.19% (-0.02%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 6a43e13 | Docs | Datadog PR Page | Give us feedback!

@BeltranBulbarellaDD

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b471339357

ℹ️ 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".


export async function computeCanvasImageHash(blob: Blob): Promise<string> {
const imageBytes = await readBlobAsArrayBuffer(blob)
const digest = await crypto.subtle.digest('SHA-256', imageBytes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide a fallback when SubtleCrypto is unavailable

issue: When canvas recording runs on a non-secure HTTP origin or another context without SubtleCrypto, crypto.subtle is unavailable and every hash attempt rejects. startCanvasCapture swallows that rejection after the canvas has already been marked clean, so canvasImageObservable never emits the frame and it is not retried until another mutation. Guard the API and use a supported fallback, or explicitly disable canvas capture before clearing dirty frames.

Useful? React with 👍 / 👎.

const canvasManager = createCanvasManager()
trackers.push(trackCanvas2DMutations(canvasManager.markCanvasDirty))
trackers.push(
trackCanvas2DMutations(canvasManager.markCanvasDirty),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Seed already-rendered canvases when recording starts

issue: If a canvas is painted before record() starts—particularly when startSessionReplayRecordingManually is enabled—and is not painted again, none of its drawing calls pass through this newly installed instrumentation. The manager starts empty, and full-snapshot serialization has no path that marks existing canvas elements dirty, so the canvas image is never emitted and the replay remains blank for that element. Seed canvases encountered by the initial snapshot, or otherwise capture their current contents when recording begins.

Useful? React with 👍 / 👎.

const captureIntervalId = setInterval(captureDirtyCanvases, ONE_SECOND / maxFramesPerSecond)

function captureDirtyCanvases() {
canvasManager.getDirtyCanvases().forEach((canvas) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip canvases that are not part of the replay DOM

issue: In applications that use detached canvases for image processing or as scratch buffers, the global 2D-context instrumentation marks those canvases dirty too, and this loop encodes and hashes every one of them even though they have no serialized replay node and cannot be displayed in the replay. Repeated drawing on such buffers can therefore add expensive PNG encoding and SHA-256 work at every sampling interval. Gate capture on the canvas being represented in the recording scope, while deferring newly drawn canvases until they are serialized.

Useful? React with 👍 / 👎.

@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support-3-capture-images branch from b471339 to 6a43e13 Compare August 14, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant