fix(🌐): keep the web canvas usable when its layout effect re-runs - #4027
Open
dennytosp wants to merge 1 commit into
Open
fix(🌐): keep the web canvas usable when its layout effect re-runs#4027dennytosp wants to merge 1 commit into
dennytosp wants to merge 1 commit into
Conversation
WebGLRenderer.dispose() called WEBGL_lose_context.loseContext() on the <canvas> element, which per spec loses that element's context permanently - a later getContext() returns the same dead context and only restoreContext() revives it. But the renderer lives in a layout effect, and effect cleanup does not mean the element is going away: StrictMode's DEV double-invoke and an Activity reveal (how React Navigation keeps inactive routes mounted) both re-run the effect on the very same node. The second construction then hands CanvasKit a dead canvas and faults inside wasm with "Cannot read properties of null (reading 'rangeMin')". What Shopify#3929 needed was CanvasKit.deleteContext, which unregisters the context and drops the reference to the detached element - that stays. loseContext only released the drawing buffer eagerly, and it was the one part of dispose() that left the element unusable. The regression test drives the component through StrictMode with a WebGL context mock that models the spec'd behaviour: once lost, the element's context stays lost. Fixes Shopify#3976
dennytosp
force-pushed
the
fix/keep-web-canvas-context-on-effect-rerun
branch
from
August 24, 2026 15:11
05cc2b4 to
0101224
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3976.
WebGLRenderer.dispose()callsWEBGL_lose_context.loseContext()on the<canvas>element. Per the spec that loses the element's context permanently — a latergetContext("webgl2")returns the same dead context object, and onlyrestoreContext()revives it.But the renderer is created and disposed inside a
useLayoutEffect, and effect cleanup does not mean the element is going away. React re-runs layout effects on a preserved host node in at least two everyday cases:reappearLayoutEffectswhen a hidden Activity/offscreen subtree is revealed — which is how React Navigation and Expo Router keep inactive routes mounted.The second construction then hands CanvasKit a dead canvas and faults inside wasm with
Cannot read properties of null (reading 'rangeMin'), taking the tree down.What #3929 actually needed is
CanvasKit.deleteContext, which unregisters the context from CanvasKit's registry and drops its reference to the detached element — that stays.loseContext()only released the drawing buffer eagerly, and it was the sole part ofdispose()that left the element unusable.StaticWebGLRenderer.cleanupRenderResult()keeps its call: that one is on a throwawayOffscreenCanvasthat is never reused.The new test in
SkiaPictureView.web.spec.tsxdrives the component throughStrictModewith a WebGL context mock that models the spec'd behaviour — once lost, the element's context stays lost, andGetWebGLContextreturns no handle for it. Onmainit fails with the reported stack:The mock is wired into the existing
getContextstub, so the four pre-existing tests now exercise a real context object instead ofnull; they still pass unchanged.