Stirling-Tools/Stirling-PDF · error · Error

Unable to acquire 2D canvas context.

Error message

Unable to acquire 2D canvas context.

What it means

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.

Source

Thrown at frontend/editor/src/core/workers/pixelCompareWorker.ts:56

  canvasContextUnavailable: "Unable to acquire 2D canvas context.",
};

// pdfjs-dist 5.x expects CanvasFactory and FilterFactory to be **class constructors**
// (it does `new CanvasFactory({ ownerDocument, enableHWA })` internally), so we build
// the class on demand with request-scoped error strings captured in its closure.
const createOffscreenCanvasFactory = (errorStrings: ErrorStrings) =>
  class OffscreenCanvasFactory {
    constructor(_opts?: { ownerDocument?: unknown; enableHWA?: boolean }) {
      /* ownerDocument/enableHWA ignored — we always use OffscreenCanvas */
    }

    create(width: number, height: number): CanvasAndContext {
      const canvas = new OffscreenCanvas(
        Math.max(1, width),
        Math.max(1, height),
      );
      const context = canvas.getContext("2d", { willReadFrequently: true });
      if (!context) throw new Error(errorStrings.canvasContextUnavailable);
      return { canvas, context };
    }

    reset(
      canvasAndContext: CanvasAndContext,
      width: number,
      height: number,
    ): void {
      canvasAndContext.canvas.width = Math.max(1, width);
      canvasAndContext.canvas.height = Math.max(1, height);
    }

    destroy(canvasAndContext: CanvasAndContext): void {
      canvasAndContext.canvas.width = 0;
      canvasAndContext.canvas.height = 0;
      (canvasAndContext as { canvas: OffscreenCanvas | null }).canvas = null;
      (
        canvasAndContext as {

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Feature-detect OffscreenCanvas.prototype.getContext before posting the worker job; fall back to a main-thread comparison if unavailable.
  2. Cap the number of concurrent page renders in runPool to limit memory pressure on the worker.
  3. In tests, provide a worker with a real 2D OffscreenCanvas backend or skip this path.
  4. Catch at the worker boundary and post a structured error the main thread can render.

Example fix

// before
const context = canvas.getContext("2d", { willReadFrequently: true });
if (!context) throw new Error(errorStrings.canvasContextUnavailable);

// after
const context = canvas.getContext("2d", { willReadFrequently: true });
if (!context) {
  throw new Error(
    "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.",
  );
}
Defensive patterns

Strategy: validation

Validate before calling

function workerSupportsOffscreen2d(): boolean {
  if (typeof OffscreenCanvas === "undefined") return false;
  try {
    const c = new OffscreenCanvas(1, 1);
    return c.getContext("2d", { willReadFrequently: true }) !== null;
  } catch {
    return false;
  }
}

if (!workerSupportsOffscreen2d()) {
  post({ type: "error", message: "OffscreenCanvas 2D is required for pixel compare." });
}

Type guard

const hasOffscreen2d = (c: OffscreenCanvas): c is OffscreenCanvas & { getContext: () => OffscreenCanvasRenderingContext2D } =>
  c.getContext("2d", { willReadFrequently: true }) !== null;

Try / catch

try {
  await compareInWorker(payload);
} catch (error) {
  if (/Unable to acquire 2D canvas context/.test((error as Error).message)) {
    // fall back to main-thread comparison or tell the user to update their browser
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: Older browser versions; testing the worker under jsdom/node where OffscreenCanvas may exist but getContext('2d') returns null; heavy comparison workloads exhausting worker memory.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/79fd23d25f072548. Report an issue: GitHub.