Skip to content

NullGraphicsDevice throws in constructor with mock canvas since v2.20.0 (getBoundingClientRect regression from #8950) #8999

Description

@willeastcott

Description

Since v2.20.0, constructing any GraphicsDevice (including NullGraphicsDevice) throws if the supplied canvas does not implement getBoundingClientRect():

TypeError: this.canvas.getBoundingClientRect is not a function
    at NullGraphicsDevice.updateClientRect
    at new GraphicsDevice
    at new NullGraphicsDevice

#8950 (first released in v2.20.0) moved the updateClientRect() call into the base GraphicsDevice constructor so that clientRect is valid before the first frame (fixing #8932). updateClientRect() calls this.canvas.getBoundingClientRect() unconditionally, which only exists on real DOM elements.

NullGraphicsDevice is the documented path for headless / server-side use, where consumers typically pass a mock canvas object. For example, @playcanvas/react passes { id: 'pc-react-mock-canvas' } to NullGraphicsDevice for SSR — and does so at module scope, so with engine ≥ 2.20.0 merely importing @playcanvas/react in Node crashes.

Impact

  • Broke the developer.playcanvas.com Docusaurus build: static site generation failed for all 639 doc pages once the site upgraded to playcanvas 2.20.4. The site is temporarily pinned to ~2.19.7 (Pin playcanvas to ~2.19.7 to fix broken build developer-site#1074) until this is fixed.
  • Any headless/SSR consumer constructing a NullGraphicsDevice with a non-DOM canvas is affected.

Steps to Reproduce

In Node (no DOM):

import { NullGraphicsDevice } from 'playcanvas';

const mockCanvas = { id: 'mock' };
new NullGraphicsDevice(mockCanvas); // throws in >= 2.20.0, works in 2.19.7

Suggested fix

Guard the constructor-time initialization so it degrades gracefully when the canvas lacks getBoundingClientRect (e.g. typeof this.canvas.getBoundingClientRect === 'function' in updateClientRect(), or skip/override for the null backend), while preserving the intent of #8950 — a valid clientRect before the first device update for real backends.

There is a related hardening issue on the consumer side: playcanvas/react's SSR mock canvas could also stub this method (issue to follow on that repo).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions