Skip to content

fix(🐛): don't crash when snapshotting a canvas that is not laid out - #4030

Open
woosanggyu wants to merge 3 commits into
Shopify:mainfrom
woosanggyu:fix/make-image-snapshot-size-guard
Open

fix(🐛): don't crash when snapshotting a canvas that is not laid out#4030
woosanggyu wants to merge 3 commits into
Shopify:mainfrom
woosanggyu:fix/make-image-snapshot-size-guard

Conversation

@woosanggyu

@woosanggyu woosanggyu commented Aug 25, 2026

Copy link
Copy Markdown

Addresses #4029

Problem

makeImageSnapshotAsync() can abort the process in two ways when the Canvas native view is not ready:

  1. View not laid out yet (production crash). RNSkMetalCanvasProvider::getWidth()/getHeight() return -1 while _ctx == nullptr. _ctx is only created by setSize() from layoutSubviews, but the view is already registered in willMoveToSuperview. A snapshot whose main-queue block runs in that window reaches RNSkView::makeImageSnapshot with size -1, which flows unvalidated into makeOffscreenSurface:
    MTLTextureDescriptor has width (18446744073709551615) greater than the maximum allowed size
    
  2. View does not exist. When ViewRegistry::getView() returns nullptr (canvas under display: "none", snapshot right after commit before Fabric mounted the view, or after unmount), the async path dereferences it (view->makeImageSnapshot(...)). The sync makeImageSnapshot already null-checks; the async variant did not.

Fix

  • RNSkView::makeImageSnapshot: return nullptr when the canvas size is not positive.
  • RNSkJsiViewApi::makeImageSnapshotAsync: null-check the view before use.

Both degrade to the existing "Failed to make snapshot from view." promise rejection.

Repro / verification

Adds API → 🫥 Snapshot Before Layout to the example app. It exercises all three cases (hidden canvas, zero-size canvas, remount + snapshot-before-layout loop).

  • On main, the remount loop aborts with the Metal assertion above within seconds (iPhone 16 Pro simulator, iOS 26.5); the hidden-canvas button aborts immediately with EXC_BAD_ACCESS.
  • With this PR, 1138 remount iterations produced 58 rejections (the guard firing) and 1080 resolved snapshots, no crash.

The existing e2e harness drives offscreen surfaces over a socket and does not exercise the RN view lifecycle, so this is kept as an example screen rather than an automated e2e test.

We have been running the size guard as a local patch in production since 2.10.1 and the crash signature disappeared.

Before the fix (main) — aborts within seconds

skia-4029-before-fix.mp4

After the fix — 30 s of the remount loop, rejections instead of a crash

skia-4029-after-fix.mp4

makeImageSnapshot passed the canvas size (-1 before layoutSubviews) into
makeOffscreenSurface, aborting in Metal texture validation, and
makeImageSnapshotAsync dereferenced a null view. Return nullptr in both
cases so they fall through to the existing snapshot rejection.

Adds an example screen (API → Snapshot Before Layout) reproducing Shopify#4029.
@woosanggyu
woosanggyu force-pushed the fix/make-image-snapshot-size-guard branch from 34eb80a to da86e60 Compare August 25, 2026 11:20
@woosanggyu

Copy link
Copy Markdown
Author

I have signed the CLA!

@wcandillon
wcandillon self-requested a review August 30, 2026 15:47
The web smoke test asserts the first canvas on the page is visible;
the hidden and zero-size repro canvases were rendered before the
visible control canvas.
@woosanggyu
woosanggyu force-pushed the fix/make-image-snapshot-size-guard branch from 9fab0a8 to 2d10f33 Compare August 31, 2026 00:20
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