{"record":{"id":"81d2bfee33ea990a","repo":"siyuan-note/siyuan","slug":"canvas-2d-is-unavailable","errorCode":null,"errorMessage":"Canvas 2D is unavailable","messagePattern":"Canvas 2D is unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/layout/dock/graph/labelRenderer.ts","lineNumber":16,"sourceCode":"import {IGraphRenderState} from \"./renderer\";\n\nconst LABEL_FONT_SIZE = 32;\nconst LABEL_CELL_WIDTH = 72;\nconst LABEL_CELL_HEIGHT = 20;\n\nexport class GraphLabelRenderer {\n    private readonly canvas: HTMLCanvasElement;\n    private readonly context: CanvasRenderingContext2D;\n    private geometryVersion = -1;\n    private labelOrder: number[] = [];\n\n    constructor(canvas: HTMLCanvasElement) {\n        const context = canvas.getContext(\"2d\");\n        if (!context) {\n            throw new Error(\"Canvas 2D is unavailable\");\n        }\n        this.canvas = canvas;\n        this.context = context;\n    }\n\n    public render(state: IGraphRenderState) {\n        const width = Math.max(1, Math.round(state.width * state.devicePixelRatio));\n        const height = Math.max(1, Math.round(state.height * state.devicePixelRatio));\n        if (this.canvas.width !== width || this.canvas.height !== height) {\n            this.canvas.width = width;\n            this.canvas.height = height;\n        }\n        if (this.geometryVersion !== state.geometryVersion) {\n            this.labelOrder = state.data.nodes\n                .filter((node) => Boolean(node.label))\n                .sort((left, right) => right.degree - left.degree || left.index - right.index)\n                .map((node) => node.index);\n            this.geometryVersion = state.geometryVersion;","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/app/src/layout/dock/graph/labelRenderer.ts#L1-L34","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst ctx = canvas.getContext('2d');\n// after\nconst ctx = canvas.getContext('2d');\nif (!ctx) { showMessage(window.siyuan.languages['No canvas support']); return null; }","handlingStrategy":"type-guard","validationCode":"const supports2D = typeof HTMLCanvasElement !== 'undefined' &&\n    typeof document.createElement('canvas').getContext('2d') === 'object';\nif (!supports2D) { /* render fallback */ }","typeGuard":"function canvas2DSupported(): boolean {\n    try { return !!document.createElement('canvas').getContext('2d'); }\n    catch { return false; }\n}","tryCatchPattern":"try { return new GraphLabelRenderer(canvas); }\ncatch (e) { showGraphFallback('2D canvas unavailable'); return null; }","preventionTips":["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."],"tags":["browser","canvas","graph","typescript","feature-detection"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}