Skip to content

fix(🐛): don't crash the web view when no WebGL surface can be created - #3996

Open
wildan-m wants to merge 1 commit into
Shopify:mainfrom
wildan-m:fix/web-webgl-context-failure-crash
Open

fix(🐛): don't crash the web view when no WebGL surface can be created#3996
wildan-m wants to merge 1 commit into
Shopify:mainfrom
wildan-m:fix/web-webgl-context-failure-crash

Conversation

@wildan-m

Copy link
Copy Markdown

Description

On web, WebGLRenderer in SkiaPictureView.web.tsx throws when the browser cannot provide a WebGL surface — hardware acceleration disabled, a blocklisted GPU, a GPU process restart, or the per-page live-context limit being exhausted. Both throw sites escape as uncatchable errors:

  • the constructor throws inside the useLayoutEffect that builds the renderer, and
  • onResize throws inside the ResizeObserver callback,

so no try/catch or React error boundary in the embedding app can reach them, and an environmental limitation becomes an app crash. We (Expensify) see this at scale in production Sentry as failed to create webgl context: err 0 / Could not create surface from chart views on /home and /search (Sentry issue APP-7MV: hundreds of users, thousands of events), currently worked around with a patch-package patch.

Change

Degrade instead of throwing, mirroring how StaticWebGLRenderer already treats a failed surface as recoverable rather than fatal:

  • When no WebGL/graphics context or on-screen surface can be created, leave the renderer inert — draw(), makeImageSnapshot() and onResize already no-op on a null surface/grContext, and dispose() already handles the null fields.
  • Dispatch a bubbling skia-surface-unavailable CustomEvent on the canvas so embedders can detect the condition and show a fallback UI (deferred one frame so listeners attached in an effect of the same commit are registered first). Without a listener the event is a no-op, so existing consumers see no behavioral change other than not crashing.

Notes

  • No API change; capable clients are unaffected.
  • The event lets an app render its own "chart unavailable" state — a capability check before mounting cannot cover this, because the surface is created after the CanvasKit WASM module loads and WebGL availability can change in between.

Test plan

  • Simulated an incapable client in Chrome by making HTMLCanvasElement.prototype.getContext return null for "webgl2": previously the uncaught throw surfaced and the canvas stayed blank; with this change the view mounts inert, the event fires on the canvas, and a listener can swap in fallback UI. Restoring WebGL and remounting renders normally.
  • Verified prettier --check (2.8.7) passes on the touched file.

On web, WebGLRenderer throws when WebGL is unavailable (hardware acceleration
disabled, blocklisted GPU, or the per-page context limit exhausted). The
constructor throw escapes the layout effect that builds the renderer, and the
onResize throw escapes the ResizeObserver callback, where no try/catch or React
error boundary can reach it - so an environment problem becomes an app crash.

Degrade to an inert renderer instead: draw() and makeImageSnapshot() already
no-op on a null surface, matching how StaticWebGLRenderer treats a failed
surface as recoverable. A bubbling "skia-surface-unavailable" CustomEvent is
dispatched on the canvas so embedders can show a fallback UI; without a
listener it is a no-op.
@wildan-m

Copy link
Copy Markdown
Author

I have signed the CLA!

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