#4017·jsPDF

Memory retention scales dramatically with raster image payload when PDF encryption is enabled

Author: saurabhzaiswalCreated Sep 6, 2026Updated Sep 13, 2026

jspdf-encryption-raster-memory-repro.zip

Description

Hi, I found a memory-retention issue when using jsPDF with raster images and PDF encryption.

The problem first appeared in an application I was working on, so I reduced it step by step to make sure it was not caused by my framework or PDF wrapper. After removing Vue, html2canvas, html2pdf.js, and the application renderer, I can still reproduce the same behavior with raw jsPDF.

When encryption is disabled, JPEG-based PDFs release almost all of the memory after generation.

When encryption.userPassword is enabled, a much larger amount of JavaScript heap stays retained after:

  • pdf.output('blob') finishes
  • references to the jsPDF instance and image data are cleared
  • the source canvas is reset
  • Chromium garbage collection is forced
  • another 2 seconds are allowed to pass, followed by another forced GC

The retained memory also grows with the number of raster pages.

Environment

  • jsPDF: 4.2.1
  • Browser: Chromium
  • OS: Windows
  • Test runner: Playwright
  • Output API: pdf.output('blob')
  • Encryption option: encryption: { userPassword: 'phase321' }

Reproduction summary

The attached reproduction:

  1. loads jspdf/dist/jspdf.umd.min.js
  2. creates a synthetic 1191 x 1461 canvas
  3. converts it to JPEG or PNG
  4. adds the same raster to N PDF pages with addImage()
  5. compares encryption OFF vs ON
  6. calls pdf.output('blob')
  7. clears references and zeroes the source canvas
  8. forces GC with Chromium CDP
  9. measures JS heap again after 2 seconds

Results

JPEG Data URL scaling

Pages Encryption OFF retained after 2s Encryption ON retained after 2s
5 ~0.4 MB retained ~18.1 MB retained
10 ~0.4 MB retained ~35.9 MB retained
15 ~0.4 MB retained ~53.6 MB retained
20 ~0.4 MB retained ~71.3 MB retained
25 ~0.4 MB retained ~89.0 MB retained

Generated PDF sizes are effectively the same between OFF and ON for each page count. For the 25-page JPEG case the PDF is about 4.44 MB in both cases.

25-page representation controls

Image input Encryption OFF retained Encryption ON retained
JPEG Data URL ~0.45 MB ~89.0 MB
JPEG Uint8Array ~4.9 MB ~93.5 MB
PNG Data URL ~125.6 MB ~2614.4 MB

The PNG case is especially severe: the generated PDF is about 124.47 MB, while the encrypted run retains about 2.55 GiB of JS heap after the forced-GC + 2-second measurement window.

Expected behavior

After PDF serialization finishes and caller-owned references are released, enabling encryption should not cause heap retention to remain roughly proportional to the embedded raster payload.

Some temporary extra allocation during encryption/serialization is expected, but the retained heap should become reclaimable after output completes and references are dropped.

Actual behavior

With encryption disabled, JPEG runs return to a small retained baseline.

With userPassword encryption enabled, retained heap grows almost linearly with page count / raster payload and remains after repeated forced GC.

The same behavior also reproduces when the JPEG is passed as a Uint8Array, so it does not appear to be specific to Data URLs.

Additional observations

-This was originally observed in an application renderer, then isolated to this raw jsPDF reproduction. -I am reporting this as memory retention rather than a confirmed permanent memory leak, because I have only measured the heap after forced GC and a 2-second wait.

Notes

  • The effect reproduces with jsPDF directly.
  • It is not specific to a framework or wrapper.
  • JPEG Data URL and JPEG Uint8Array both reproduce it.
  • PNG makes the effect dramatically larger.
  • PDF generation itself succeeds and Blob sizes are valid.
  • This was originally observed in an application renderer, then isolated to this raw jsPDF reproduction.

Minimal reproducible example

I created a small standalone reproduction that uses only jsPDF.

It does not use:

  • Vue
  • html2canvas
  • html2pdf.js
  • any PDF wrapper

The reproduction:

  1. Creates a synthetic browser canvas
  2. Converts it to JPEG or PNG
  3. Creates a jsPDF document
  4. Adds the image to multiple pages using addImage()
  5. Runs the same test with encryption OFF and ON
  6. Calls pdf.output('blob')
  7. Clears the references
  8. Forces Chromium GC
  9. Measures JS heap again after 2 seconds

I have attached the reproduction files.

To run it:

npm install
npx playwright install chromium
npm test

The test prints retained heap for security OFF and ON and writes results.json.

If this is expected behavior, or if there is a recommended way to avoid retaining the encrypted raster data after serialization, I would appreciate guidance.

I can also provide additional heap measurements or test a proposed fix if that would help.