{"record":{"id":"79fd23d25f072548","repo":"Stirling-Tools/Stirling-PDF","slug":"unable-to-acquire-2d-canvas-context","errorCode":null,"errorMessage":"Unable to acquire 2D canvas context.","messagePattern":"Unable to acquire 2D canvas context\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/workers/pixelCompareWorker.ts","lineNumber":56,"sourceCode":"  canvasContextUnavailable: \"Unable to acquire 2D canvas context.\",\n};\n\n// pdfjs-dist 5.x expects CanvasFactory and FilterFactory to be **class constructors**\n// (it does `new CanvasFactory({ ownerDocument, enableHWA })` internally), so we build\n// the class on demand with request-scoped error strings captured in its closure.\nconst createOffscreenCanvasFactory = (errorStrings: ErrorStrings) =>\n  class OffscreenCanvasFactory {\n    constructor(_opts?: { ownerDocument?: unknown; enableHWA?: boolean }) {\n      /* ownerDocument/enableHWA ignored — we always use OffscreenCanvas */\n    }\n\n    create(width: number, height: number): CanvasAndContext {\n      const canvas = new OffscreenCanvas(\n        Math.max(1, width),\n        Math.max(1, height),\n      );\n      const context = canvas.getContext(\"2d\", { willReadFrequently: true });\n      if (!context) throw new Error(errorStrings.canvasContextUnavailable);\n      return { canvas, context };\n    }\n\n    reset(\n      canvasAndContext: CanvasAndContext,\n      width: number,\n      height: number,\n    ): void {\n      canvasAndContext.canvas.width = Math.max(1, width);\n      canvasAndContext.canvas.height = Math.max(1, height);\n    }\n\n    destroy(canvasAndContext: CanvasAndContext): void {\n      canvasAndContext.canvas.width = 0;\n      canvasAndContext.canvas.height = 0;\n      (canvasAndContext as { canvas: OffscreenCanvas | null }).canvas = null;\n      (\n        canvasAndContext as {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/workers/pixelCompareWorker.ts#L38-L74","documentation":"Thrown inside the OffscreenCanvasFactory.create method (a custom pdfjs CanvasFactory for the Web Worker) when new OffscreenCanvas(...).getContext(\"2d\", {willReadFrequently:true}) returns null. This is the factory pdfjs-dist 5.x instantiates with `new CanvasFactory(...)` to render pages. A null 2D context inside a worker means OffscreenCanvas 2D is unavailable (older browser/worker), WASM/canvas memory exhausted, or the worker global lacks OffscreenCanvas.","triggerScenarios":"Running the pixel-compare worker in a browser that supports transferable OffscreenCanvas but not its 2D context (rare); the worker is polyfilled/mocked in a test without a real 2D backend; memory exhaustion after many page renders.","commonSituations":"Older browser versions; testing the worker under jsdom/node where OffscreenCanvas may exist but getContext('2d') returns null; heavy comparison workloads exhausting worker memory.","solutions":["Feature-detect OffscreenCanvas.prototype.getContext before posting the worker job; fall back to a main-thread comparison if unavailable.","Cap the number of concurrent page renders in runPool to limit memory pressure on the worker.","In tests, provide a worker with a real 2D OffscreenCanvas backend or skip this path.","Catch at the worker boundary and post a structured error the main thread can render."],"exampleFix":"// before\nconst context = canvas.getContext(\"2d\", { willReadFrequently: true });\nif (!context) throw new Error(errorStrings.canvasContextUnavailable);\n\n// after\nconst context = canvas.getContext(\"2d\", { willReadFrequently: true });\nif (!context) {\n  throw new Error(\n    \"Unable to acquire 2D canvas context: this browser/worker does not support OffscreenCanvas 2D. Use a modern browser or fall back to main-thread comparison.\",\n  );\n}","handlingStrategy":"validation","validationCode":"function workerSupportsOffscreen2d(): boolean {\n  if (typeof OffscreenCanvas === \"undefined\") return false;\n  try {\n    const c = new OffscreenCanvas(1, 1);\n    return c.getContext(\"2d\", { willReadFrequently: true }) !== null;\n  } catch {\n    return false;\n  }\n}\n\nif (!workerSupportsOffscreen2d()) {\n  post({ type: \"error\", message: \"OffscreenCanvas 2D is required for pixel compare.\" });\n}","typeGuard":"const hasOffscreen2d = (c: OffscreenCanvas): c is OffscreenCanvas & { getContext: () => OffscreenCanvasRenderingContext2D } =>\n  c.getContext(\"2d\", { willReadFrequently: true }) !== null;","tryCatchPattern":"try {\n  await compareInWorker(payload);\n} catch (error) {\n  if (/Unable to acquire 2D canvas context/.test((error as Error).message)) {\n    // fall back to main-thread comparison or tell the user to update their browser\n  }\n  throw error;\n}","preventionTips":["Feature-detect OffscreenCanvas 2D before posting the worker job.","Cap concurrent page renders in runPool to bound memory.","Provide a main-thread fallback comparison for unsupported workers."],"tags":["offscreen-canvas","web-worker","browser-env","pdfjs","memory"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}