{"record":{"id":"095ebbdf9f0b201c","repo":"Stirling-Tools/Stirling-PDF","slug":"could-not-get-canvas-context","errorCode":null,"errorMessage":"Could not get canvas context","messagePattern":"Could not get canvas context","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/hooks/usePDFProcessor.ts","lineNumber":62,"sourceCode":"  );\n\n  // Internal function to generate thumbnail from already-opened PDF\n  const generateThumbnailFromPDF = useCallback(\n    async (\n      pdf: any,\n      pageNumber: number,\n      scale: number = 0.5,\n    ): Promise<string> => {\n      const page = await pdf.getPage(pageNumber);\n\n      const viewport = page.getViewport({ scale });\n      const canvas = document.createElement(\"canvas\");\n      canvas.width = viewport.width;\n      canvas.height = viewport.height;\n\n      const context = canvas.getContext(\"2d\");\n      if (!context) {\n        throw new Error(\"Could not get canvas context\");\n      }\n\n      await page.render({ canvasContext: context, viewport }).promise;\n      return canvas.toDataURL();\n    },\n    [],\n  );\n\n  const processPDFFile = useCallback(\n    async (file: File): Promise<PDFDocument> => {\n      setLoading(true);\n      setError(null);\n\n      try {\n        const arrayBuffer = await file.arrayBuffer();\n        const pdf = await pdfWorkerManager.createDocument(arrayBuffer);\n        const totalPages = pdf.numPages;\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/hooks/usePDFProcessor.ts#L44-L80","documentation":"`HTMLCanvasElement.getContext('2d')` returns null instead of throwing when the 2D rendering context cannot be acquired. This guard fires in `generateThumbnailFromPDF` (and the sibling `generatePageThumbnail`), which both create a throwaway canvas to render a pdf.js page into a data-URL thumbnail. A null return means the browser refused to hand out a 2D context for that canvas element.","triggerScenarios":"Calling `generatePageThumbnail`/`generateThumbnailFromPDF` in a tight loop (e.g. generating thumbnails for a 100+ page PDF) exhausts the browser's per-tab canvas-context budget; Safari/WebKit hard-caps live contexts. It also returns null in headless/SSR environments where `document.createElement('canvas')` has no GPU/2D backend, or if `OffscreenCanvas`/a non-2D context was already requested on that element.","commonSituations":"Thumbnail generation for large PDFs; running the editor in a headless browser (Puppeteer/Playwright) or an environment without a real compositor; Safari with many simultaneous canvases; memory pressure causing the browser to deny context allocation.","solutions":["Recycle a single shared canvas across thumbnail renders instead of calling `document.createElement('canvas')` per page, and reset it via `context.clearRect(0,0,canvas.width,canvas.height)` between renders.","Rate-limit concurrent thumbnail generation (queue with a small concurrency cap, e.g. 3-4) so the live-context budget is never exhausted.","Before rendering, guard `const ctx = canvas.getContext('2d'); if (!ctx) return placeholderThumbnail();` to degrade gracefully rather than aborting the whole file.","For headless test environments, configure the runner with `--use-gl=swiftshader` / a software WebGL backend so canvas contexts are allocatable."],"exampleFix":"// before\nconst canvas = document.createElement(\"canvas\");\ncanvas.width = viewport.width;\ncanvas.height = viewport.height;\nconst context = canvas.getContext(\"2d\");\nif (!context) {\n  throw new Error(\"Could not get canvas context\");\n}\n\n// after (graceful degradation + reusable canvas)\nconst context = canvas.getContext(\"2d\");\nif (!context) {\n  console.warn(`Canvas 2D context unavailable for page ${pageNumber}; using placeholder`);\n  return PLACEHOLDER_THUMBNAIL_DATA_URL;\n}","handlingStrategy":"fallback","validationCode":"// Check before requesting the context\nfunction canGet2dContext(): boolean {\n  try {\n    const probe = document.createElement(\"canvas\");\n    return probe.getContext(\"2d\") !== null;\n  } catch {\n    return false;\n  }\n}\n\n// Concurrency-cap thumbnail generation so the live-context budget isn't hit\nconst MAX_CONCURRENT_THUMBS = 4;\nasync function pool<T>(items: T[], n: number, fn: (t: T) => Promise<T>): Promise<T[]> { /* simple semaphore */ return items; }","typeGuard":"// A canvas whose 2D context is actually available\nfunction hasUsable2dContext(canvas: HTMLCanvasElement): canvas is HTMLCanvasElement & { getContext(c: \"2d\"): CanvasRenderingContext2D } {\n  return canvas.getContext(\"2d\") !== null;\n}","tryCatchPattern":"try {\n  const context = canvas.getContext(\"2d\");\n  if (!context) return PLACEHOLDER_THUMBNAIL_DATA_URL;\n  await page.render({ canvasContext: context, viewport }).promise;\n  return canvas.toDataURL();\n} catch (e) {\n  console.warn('Thumbnail render failed, using placeholder', e);\n  return PLACEHOLDER_THUMBNAIL_DATA_URL;\n}","preventionTips":["Reuse a single shared canvas across a batch of thumbnail renders instead of allocating one per page.","Cap concurrent thumbnail renders (e.g. 3-4) to stay under the browser's context budget.","Always provide a placeholder data-URL fallback so one missing context never aborts the whole file.","In headless test runners, enable a software GL backend (`--use-gl=swiftshader`)."],"tags":["canvas","browser","rendering","pdfjs","thumbnails"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}