siyuan-note/siyuan · error · Error
Canvas 2D is unavailable
Error message
Canvas 2D is unavailable
What it means
GraphLabelRenderer constructor calls canvas.getContext('2d') and throws when it returns null. A null 2D context means the browser could not allocate a CanvasRenderingContext2D — typically because canvas contexts are exhausted, the canvas was already assigned an incompatible context type, or the environment lacks 2D canvas support entirely.
Source
Thrown at app/src/layout/dock/graph/labelRenderer.ts:16
import {IGraphRenderState} from "./renderer";
const LABEL_FONT_SIZE = 32;
const LABEL_CELL_WIDTH = 72;
const LABEL_CELL_HEIGHT = 20;
export class GraphLabelRenderer {
private readonly canvas: HTMLCanvasElement;
private readonly context: CanvasRenderingContext2D;
private geometryVersion = -1;
private labelOrder: number[] = [];
constructor(canvas: HTMLCanvasElement) {
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Canvas 2D is unavailable");
}
this.canvas = canvas;
this.context = context;
}
public render(state: IGraphRenderState) {
const width = Math.max(1, Math.round(state.width * state.devicePixelRatio));
const height = Math.max(1, Math.round(state.height * state.devicePixelRatio));
if (this.canvas.width !== width || this.canvas.height !== height) {
this.canvas.width = width;
this.canvas.height = height;
}
if (this.geometryVersion !== state.geometryVersion) {
this.labelOrder = state.data.nodes
.filter((node) => Boolean(node.label))
.sort((left, right) => right.degree - left.degree || left.index - right.index)
.map((node) => node.index);
this.geometryVersion = state.geometryVersion;View on GitHub (pinned to 251596fc0d)
Solutions
- Use a fresh <canvas> element for the label renderer; do not share it with the WebGL renderer.
- Free prior canvas contexts (set canvas width/height to 0 and remove the element) before requesting a new one to avoid the per-page context limit.
- Feature-detect 2D canvas support before opening the graph dock and show a fallback message if unavailable.
- In tests, polyfill canvas (e.g. node-canvas / jest-canvas-mock) instead of running under jsdom without one.
Example fix
// before
const ctx = canvas.getContext('2d');
// after
const ctx = canvas.getContext('2d');
if (!ctx) { showMessage(window.siyuan.languages['No canvas support']); return null; } Defensive patterns
Strategy: type-guard
Validate before calling
const supports2D = typeof HTMLCanvasElement !== 'undefined' &&
typeof document.createElement('canvas').getContext('2d') === 'object';
if (!supports2D) { /* render fallback */ } Type guard
function canvas2DSupported(): boolean {
try { return !!document.createElement('canvas').getContext('2d'); }
catch { return false; }
} Try / catch
try { return new GraphLabelRenderer(canvas); }
catch (e) { showGraphFallback('2D canvas unavailable'); return null; } Prevention
- Feature-detect canvas 2D before opening the graph dock.
- Use a dedicated canvas element for the label renderer; never share with the webgl canvas.
- Release offscreen canvases when closing panels to stay under the context limit.
- In tests, polyfill canvas (jest-canvas-mock) instead of relying on jsdom.
When it happens
Trigger: Constructing GraphLabelRenderer on a canvas that already obtained a webgl2 context (context type mismatch), in a headless/test environment without a real 2D canvas implementation, after leaking too many canvas contexts so the browser refuses new ones, or in a webview that disables 2D canvas.
Common situations: Old Android WebView or stripped-down Electron builds with canvas disabled; automated tests in jsdom (no real canvas); reused canvas element after webgl context; very long-running sessions that leaked offscreen canvases.
Related errors
- WebGL2 is unavailable
- Unable to link graph shader
- Unable to compile graph shader
- Unable to allocate graph rendering resource
- AudioContext is not supported
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/81d2bfee33ea990a.
Report an issue: GitHub.