Skip to content

Conversation

JustinRatner
Copy link

There is a race condition when using virtualDOM with canvas elements. Images load asynchronously, so the library may end up applying the incremental updates to canvas before the image at rr_dataURL has loaded. Once the image loads, it overrides any drawImage commands that may have occurred in the mutations.

This change makes the initial drawImage of rr_dataURL function as if it were the first mutation eliminating the race condition.

@Copilot Copilot AI review requested due to automatic review settings October 10, 2025 00:03
Copy link

changeset-bot bot commented Oct 10, 2025

⚠️ No Changeset found

Latest commit: 6958223

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a race condition in canvas rendering where asynchronous image loading from rr_dataURL could override incremental canvas mutations. The fix treats the initial rr_dataURL as a synthetic mutation applied synchronously before other mutations.

  • Creates a synthetic canvas mutation for rr_dataURL that executes synchronously
  • Applies this synthetic mutation before processing regular canvas mutations
  • Eliminates the race condition between async image loading and incremental updates

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
packages/rrdom/src/diff.ts Replaces async image loading with synchronous synthetic mutation for rr_dataURL
packages/rrdom/test/diff.test.ts Adds comprehensive tests validating the race condition fix and mutation ordering

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

}
// Create a synthetic canvas mutation for the initial dataURL
const syntheticMutation: canvasMutationData = {
source: 9, // IncrementalSource.CanvasMutation
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Replace magic number 9 with the actual IncrementalSource.CanvasMutation enum value for better code maintainability and readability.

Copilot uses AI. Check for mistakes.

replayer.applyCanvas(
{
timestamp: 0,
type: 3, // EventType.IncrementalSnapshot
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Replace magic number 3 with the actual EventType.IncrementalSnapshot enum value for better code maintainability and readability.

Copilot uses AI. Check for mistakes.

Comment on lines 261 to 282
// Create a synthetic canvas mutation for the initial dataURL
const syntheticMutation: canvasMutationData = {
source: 9, // IncrementalSource.CanvasMutation
id: replayer.mirror.getId(oldTree),
type: 0, // CanvasContext['2D']
commands: [{
property: 'drawImage',
args: [rrCanvasElement.rr_dataURL, 0, 0, (oldTree as HTMLCanvasElement).width, (oldTree as HTMLCanvasElement).height],
setter: false
} as any]
};
image.src = rrCanvasElement.rr_dataURL;

// Apply the synthetic mutation synchronously
replayer.applyCanvas(
{
timestamp: 0,
type: 3, // EventType.IncrementalSnapshot
data: syntheticMutation
} as canvasEventWithTime,
syntheticMutation,
oldTree as HTMLCanvasElement,
);
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The hardcoded dimensions (0, 0, canvas.width, canvas.height) may not match the original image dimensions. Consider using the actual image dimensions from rr_dataURL to maintain visual fidelity.

Copilot uses AI. Check for mistakes.

expect(firstCall.event.type).toBe(EventType.IncrementalSnapshot);
expect(firstCall.mutation.source).toBe(IncrementalSource.CanvasMutation);
expect(firstCall.mutation.commands[0].property).toBe('drawImage');
expect(firstCall.mutation.commands[0].args).toEqual([testDataURL, 0, 0, 300, 150]);
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The expected dimensions [300, 150] don't match the canvas dimensions set in the test (200x100). This should use the actual canvas dimensions for consistency.

Suggested change
expect(firstCall.mutation.commands[0].args).toEqual([testDataURL, 0, 0, 300, 150]);
expect(firstCall.mutation.commands[0].args).toEqual([testDataURL, 0, 0, 200, 100]);

Copilot uses AI. Check for mistakes.

expect(event.timestamp).toBe(0);
expect(mutation.source).toBe(IncrementalSource.CanvasMutation);
expect(mutation.commands[0].property).toBe('drawImage');
expect(mutation.commands[0].args).toEqual([testDataURL, 0, 0, 300, 150]);
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The expected dimensions [300, 150] don't match the canvas dimensions set in the test (100x100). This should use the actual canvas dimensions for consistency.

Suggested change
expect(mutation.commands[0].args).toEqual([testDataURL, 0, 0, 300, 150]);
expect(mutation.commands[0].args).toEqual([testDataURL, 0, 0, canvas.width, canvas.height]);

Copilot uses AI. Check for mistakes.

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