Skip to content

fix(viewer): guard 3D viewer against null WebGL context (Sentry MONOREPO-EDITOR-59)#455

Open
anton-pascal wants to merge 1 commit into
mainfrom
fix/sentry-EDITOR-59
Open

fix(viewer): guard 3D viewer against null WebGL context (Sentry MONOREPO-EDITOR-59)#455
anton-pascal wants to merge 1 commit into
mainfrom
fix/sentry-EDITOR-59

Conversation

@anton-pascal

@anton-pascal anton-pascal commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Sentry MONOREPO-EDITOR-59

TypeError: Cannot read properties of null (reading 'getSupportedExtensions')5282 events / 0 users.

The 0-users signature is the tell: this is headless bot / crawler traffic (plus GPU-blocklisted or hardware-acceleration-disabled browsers) hitting the public /viewer/:id route.

Root cause

Stack: three.js three.webgpu.jsBackend/Renderer init → WebGPU is unavailable so three.js transparently falls back to a WebGL backendcanvas.getContext('webgl2' | 'webgl') returns null → three calls gl.getSupportedExtensions() on the null context and throws.

The viewer already has a capability gate (canMountGpuViewer, added in #403) plus an UnsupportedGpuViewerFallback. But the gate mounted the canvas whenever 'gpu' in navigator was truthy:

if (!('gpu' in navigator) && !canCreateWebGLContext()) return false

That navigator.gpu flag being present does not guarantee WebGPU can produce a device. On headless/blocklisted browsers the adapter request fails, three falls back to WebGL, and if no real WebGL context is obtainable either, renderer.init() throws on the null context — the crash above. So the "optimistic" branch let exactly the crashing population through.

The fix

Require an actually obtainable WebGL context before mounting, unconditionally:

function canMountGpuViewer() {
  if (typeof window === 'undefined') return false
  if (!canCreateWebGLContext()) return false
  return true
}

Rationale: the WebGPURenderer fallback path always needs WebGL, so a creatable WebGL context is the true, verifiable precondition for mounting. This converts the flaky "gpu-in-navigator" optimism into a hard gate:

  • Headless bots / blocklisted browsers (no creatable context) now hit the existing UnsupportedGpuViewerFallback UI instead of crashing.
  • Real WebGPU-capable browsers always expose WebGL too, so no capable device is newly excluded.

Minimal, low-risk: one predicate changed, reusing the existing canCreateWebGLContext() helper and the existing fallback UI. No renderer/init refactor.

Scope

Do not merge without review.


Note

Low Risk
Single predicate change in the viewer capability gate with existing fallback UI; no auth or data paths touched.

Overview
Tightens the public viewer mount gate so environments that cannot create a real WebGL context never mount the WebGPU canvas.

canMountGpuViewer no longer treats 'gpu' in navigator as enough to proceed. It now always requires canCreateWebGLContext() to pass, because three.js WebGPU init can fall back to WebGL and then crash on a null GL context when neither path works (headless crawlers on /viewer/:id). Those clients should hit the existing UnsupportedGpuViewerFallback instead of throwing during renderer.init().

Inline comments document the Sentry issue and rationale; behavior for normal browsers with WebGPU + WebGL is unchanged.

Reviewed by Cursor Bugbot for commit c80734d. Bugbot is set up for automated code reviews on this repo. Configure here.

…er (MONOREPO-EDITOR-59)

Guard the /viewer/:id route against the null-GL-context crash:
TypeError: Cannot read properties of null (reading 'getSupportedExtensions')
(Sentry MONOREPO-EDITOR-59, 5282 events / 0 users = headless bots &
GPU-blocklisted browsers).

canMountGpuViewer() previously mounted whenever 'gpu' in navigator was
truthy. That flag does not guarantee WebGPU can produce a device: on
headless/blocklisted browsers the adapter request fails and three.js
falls back to a WebGL backend. With no obtainable WebGL context, three
calls getSupportedExtensions() on the null context inside renderer.init()
and throws.

Since the WebGPURenderer fallback path always needs WebGL, an obtainable
WebGL context is the real precondition for mounting. Require it
unconditionally so bots hit the existing UnsupportedGpuViewerFallback
instead of crashing. Real WebGPU-capable browsers always expose WebGL,
so no capable device is newly excluded.
@anton-pascal

Copy link
Copy Markdown
Contributor Author

Nightly Sentry triage (2026-07-11) — EDITOR-59 recurred with a new latest event (issue 7415398488) but this time from transaction /editor/:projectId (Edge 144 / Windows), not just the public /viewer/:id route. Same root cause: three.js WebGPURenderer falls back to WebGLBackend and gl.getSupportedExtensions() runs on a null GL context (WebGLBackend.initnew WebGLExtensions).

Heads up that the guard in this PR touches packages/viewer, but the editor route instantiates its own new WebGPURenderer(...) (e.g. the FloorPlanModal / editor canvas) that isn't covered here. Consider extending the null-context guard to the shared renderer-creation helper so both entry points are protected. No duplicate PR opened.

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