Skip to content

⚗️ Add canvas change detection for Session Replay - #4949

Open
BeltranBulbarellaDD wants to merge 20 commits into
mainfrom
beltran.bulbarella/canvas_support-2-prefilter
Open

⚗️ Add canvas change detection for Session Replay#4949
BeltranBulbarellaDD wants to merge 20 commits into
mainfrom
beltran.bulbarella/canvas_support-2-prefilter

Conversation

@BeltranBulbarellaDD

@BeltranBulbarellaDD BeltranBulbarellaDD commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Motivation

Session Replay canvas capture can be expensive. Before capturing a canvas, we need to know whether its bitmap or relevant DOM state changed so unchanged canvases can be skipped and bandwidth can be reduced.

Depends on #4947.

Design references:

Changes

  • Add a WeakSet-backed canvas manager that treats new canvases as dirty and tracks canvases that have already been marked clean without retaining detached elements.
  • Enable canvas dirty tracking only when canvas recording is enabled with a positive maximum frame rate.
  • Instrument bitmap-changing 2D methods, including fillRect, drawImage, fill, stroke, putImageData, clearRect, and related methods.
  • Use the existing MutationObserver to detect canvas width/height changes across property and attribute mutation paths, including reinsertion and inserted subtrees.
  • Preserve attributeNamespace so namespaced attributes such as x:width are not mistaken for the real canvas width content attribute.
  • Keep the canvas manager synchronized for mutations delivered through the observer and for records still queued when mutations are flushed.
  • Add unit coverage for dirty-state behavior, 2D instrumentation, mutation-observer detection, namespaced attributes, inserted canvases, shadow roots, and recorder activation.
  • Add an end-to-end scenario covering newly inserted canvases, drawing changes, resize changes, and non-drawing operations.
Screenshot 2026-08-19 at 13 25 47

Scope

This PR adds the dirty-canvas prefilter and change detection. It does not yet implement periodic toBlob() capture, image downscaling, hash comparison, or sending canvas images. Those pieces can use the manager to capture only canvases currently considered dirty and mark them clean after processing.

The E2E scenario uses a test-only probe of the recorder's internal WeakSet because canvas image capture and request assertions are not part of this change yet.

Validation

yarn test:unit --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/trackers/trackMutation.spec.ts --spec packages/browser-rum/src/domain/record/record.spec.ts
yarn typecheck
  • Targeted unit tests: 98 passed.
  • Type checking: passed.
  • Playwright coverage was added in test/e2e/scenario/recorder/canvas.scenario.ts; it was not run in this validation pass.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 14, 2026

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 181.27 KiB 181.27 KiB 0 B 0.00%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 21.12 KiB 22.26 KiB +1.13 KiB +5.36% ⚠️
Logs 57.50 KiB 57.50 KiB 0 B 0.00%
Rum Salesforce N/A 139.22 KiB N/A N/A N/A
Rum Slim 139.21 KiB 139.21 KiB 0 B 0.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%
Rum Shopify N/A 202.14 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

⚠️ The increase is particularly high and exceeds 5.00%. Please check the changes.

@datadog-datadog-prod-us1

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

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 77.14%
Overall Coverage: 77.22% (-0.00%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e9e087d | Docs | View more details | Give us feedback!

@BeltranBulbarellaDD BeltranBulbarellaDD changed the title Beltran.bulbarella/canvas support 2 prefilter ⚗️ Add canvas change detection for Session Replay Aug 14, 2026
@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: 685c28e8ed

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

Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support_1-initconfig branch from 1e132ae to 1bc1fbf Compare August 14, 2026 14:53
@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support-2-prefilter branch from 434f477 to ad260aa Compare August 14, 2026 14:53
@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support_1-initconfig branch from 3ec079f to 3a2a2d7 Compare August 17, 2026 09:19
@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support-2-prefilter branch from ad260aa to 07a7e6c Compare August 17, 2026 09:19
Base automatically changed from beltran.bulbarella/canvas_support_1-initconfig to main August 17, 2026 09:38
@BeltranBulbarellaDD
BeltranBulbarellaDD force-pushed the beltran.bulbarella/canvas_support-2-prefilter branch from 3a78417 to dc6b3d5 Compare August 17, 2026 09:38
@BeltranBulbarellaDD
BeltranBulbarellaDD marked this pull request as ready for review August 17, 2026 09:40
@BeltranBulbarellaDD
BeltranBulbarellaDD requested review from a team as code owners August 17, 2026 09:40

@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: dc6b3d5c72

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

Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/canvas/canvasManager.ts Outdated

@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: b056d28805

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

Comment thread packages/browser-rum-core/src/browser/domMutationObservable.ts Outdated
Comment thread test/e2e/scenario/recorder/canvas.scenario.ts Outdated
@sbarrio
sbarrio requested a review from thomas-lebeau August 18, 2026 07:07

@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: b97a735017

ℹ️ 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 function createCanvasManager(): CanvasManager {
const dirtyCanvases = new Set<HTMLCanvasElement>()

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 Avoid strongly retaining detached dirty canvases

issue: When an enabled recording observes canvas churn (for example, charts that create, draw, and remove canvases), this Set keeps every removed dirty canvas and its backing bitmap reachable. Removal mutations never delete canvases, and no production code in this commit calls getDirtyCanvases() or clearDirtyCanvases(), so the pruning inside the getter never runs and memory grows for the lifetime of the recording; detached canvases need weak bookkeeping or cleanup when they are removed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is accurate, though the headline ("avoid strongly retaining...") points in the wrong direction, I think. You need to iterate the contents of this set, it seems, so strongly retaining is necessary. However, you should have a way to remove canvases from the set of dirty canvas, and you should perform this removal in trackMutation.ts when canvas elements are removed from the DOM.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think I get what you mean but I think that's scope for the next PR. Something like this?

Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum-core/src/browser/domMutationObservable.ts Outdated
Comment thread packages/browser-rum/src/domain/record/canvas/canvasManager.spec.ts Outdated
}

export function createCanvasManager(): CanvasManager {
const dirtyCanvases = new Set<HTMLCanvasElement>()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is accurate, though the headline ("avoid strongly retaining...") points in the wrong direction, I think. You need to iterate the contents of this set, it seems, so strongly retaining is necessary. However, you should have a way to remove canvases from the set of dirty canvas, and you should perform this removal in trackMutation.ts when canvas elements are removed from the DOM.

Comment thread packages/browser-rum/src/domain/record/trackers/trackMutation.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackMutation.ts Outdated
Comment thread packages/browser-rum/src/domain/record/record.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated

@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: fe5e50cbc9

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

Comment on lines +142 to +144
if (isCanvasElement(element)) {
transaction.scope.canvasManager.markCanvasDirty(element)
}

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 canvas bookkeeping when canvas capture is disabled

issue: When sessionReplayCanvasRecording is absent, disabled, or configured with a zero frame rate, serialization still unconditionally adds every canvas to the manager's strongly held Set. Fresh evidence in the final diff is that the manager is now always created, while the canvas tracker is the only component gated by configuration; because no production path calls getDirtyCanvases() or clearDirtyCanvases(), applications that repeatedly create and remove canvases retain those elements and their backing bitmaps for the recording's lifetime even though canvas capture is off. Gate these serialization and mutation updates on the enabled configuration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is on purpose. See

Comment on lines +46 to +50
if (typeof CanvasRenderingContext2D !== 'undefined') {
CANVAS_2D_DRAWING_METHODS.forEach((method) => {
instrumentationStoppers.push(
instrumentMethod(CanvasRenderingContext2D.prototype, method, ({ target: context, onPostCall }) => {
onPostCall(() => markCanvasDirty(context.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 Mark restored 2D contexts dirty

issue: When a browser loses and later restores a 2D canvas context after that canvas has been marked clean, restoration resets the context state and backing bitmap without calling any of these instrumented drawing methods or changing a size attribute. The manager therefore continues reporting the canvas as clean, so replay can retain the pre-loss frame indefinitely unless the application subsequently performs another tracked draw; listen for the canvas contextlost/contextrestored events and mark the affected canvas dirty.

Useful? React with 👍 / 👎.

@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: feca8f2c1e

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

Comment on lines +120 to +122
if (isCanvasElement(node)) {
transaction.scope.canvasManager.markCanvasClean(node)
}

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 Clean up dirty canvases skipped by serialization

issue: When a canvas is appended, drawn into, and removed before the mutation batch is processed, drawing adds it to dirtyCanvases, but the node has no serialized ID, so processRemovedNodes() returns at the preceding nodeId === undefined check and never reaches this cleanup. Fresh evidence after the earlier retention report is that the new removal cleanup still excludes exactly these unserialized nodes; because no production path currently calls getDirtyCanvases() to prune them, repeated transient canvases and their backing bitmaps remain retained for the recording lifetime.

Useful? React with 👍 / 👎.

emitStats: EmitStatsCallback,
scope: RecordingScope
): void {
markCanvasDirtyFromMutationRecords(mutations, scope.canvasManager)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We want a deeper integration than this. What I'd do is to remove markCanvasDirtyFromMutationRecords totally, and instead handle the work you need to perform in processAttributeMutations(). (Probably around the point in the loop where you see if (attributeName === 'value').)

Also, given that we currently don't capture any information about attribute namespaces in the recording, I'd recommend simply dropping those changes in this PR. The recorder generally does not work correctly with XHTML documents today, and I don't think we should try to solve that problem as part of these changes; it's a separate issue. I'm telling you this as part of this comment so that you don't worry about the fact that you don't have access to the XML namespace of the mutated attributes in processAttributeMutations() -- that's OK for now.

const nodeId = nodeIds.get(node)
if (nodeId === undefined) {
continue // This node wasn't serialized.
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please don't remove these lines; we should never be tracking any information about nodes that don't have node ids. You probably felt the need to do this because in trackCanvas.ts you are unconditionally adding canvases to the dirty set when they are mutated; the right approach is to only do that if the canvas element has a node id already, and to additional mark canvases as unconditionally dirty when they are first serialized (i.e., when they are first assigned a node id).

Comment thread packages/browser-rum/src/domain/record/trackers/trackCanvas.ts Outdated
Comment thread packages/browser-rum/src/domain/record/record.ts Outdated
Comment thread packages/browser-rum/src/domain/record/recordingScope.ts Outdated

@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: e9e087d596

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

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.

2 participants