Search code examples
javascriptjspdf

JSPDF Memory Issue


Me and my team facing one issue of memory in our application. The issue is whenever we trigger jsPDF instance its works but after that it's holding lot of memory and we facing performance issue because its never release that memory after completion of task. For reference i prepare one example and you can check with that. https://stackblitz.com/edit/web-platform-vkewvw?file=index.html

So in this example you see my memory footprint its some around 36 MB you can see on first screen shotenter image description here

and after run the code its goes around 56MB and its not releasing the memory you see in the next screen shot. enter image description here

Can any one help on that how we overcome that problem we tried with iframe itself by not working properly.

Your help is appreciable for us.


Solution

  • As raised in comment, altering the timing of revoking blob urls may improve memory handling in an application using JSPDF, but does not come with a guarantee to do so ...


    The Blob Store

    User agents maintain a blob ULR store that keeps a reference to Blob objects keyed by the urls for them returned from URL.createObjectURL(blob). Holding a reference in the store stops the blob object from being garbage collected from memory even if JavaScript code has not kept a reference to the blob object itself.

    The blob object can be removed from the URL store by calling URL.revokeObjectURL(blobURL), after which the blob is eligible for garbage collection from memory provided no reference to it is reachable in JS.

    Now jsPDF sets its global object to window in browsers' main JavaScript thread in globalObject.js, and imports the global object as _global in FileSaver.js.

    Lines 85 and 86 of FileSaver.js define the module's saveAs export as

    var saveAs =
        _global.saveAs ||
        ... code for saving file
    

    which implies you should be able to shim the saveAs function for experimental purposes by declaring a shim (using function saveAs or window.saveAs =) in file level script before including jsPDF.js in the HTML document.

    Initially you could use the original saveAs code with console logs to demonstrate the shimming process works and that jsPDF still works. Things I would want to look at include

    • is jsPDF synchronous - meaning does it only return to the caller after clicking the link in saveAs to save the PDF file produced?
    • If it's potentially asynchronous, how to arrange a callback or promise resolution from the shim after clicking the link to prevent sequentially produced PDF files being processed in parallel.
    • Does reducing the time before revoking the blob's URL, currently set to 40 seconds for most browsers in line 188 of FileSave.js (linked above), materially affect performance of the application?
    • How well does the application run in Safari and ChromeIOS, which receive exceptional support in FileSave.js?

    Synchronous Revocation of Blob URLs

    There is some possibility that Blob URLs can be revoked synchronously after use. The following code (which doesn't work as a code snippet) creates a blob and downloads it:

    function blobURL(string) {
        const blob = new Blob(Array.from(string));
        return URL.createObjectURL(blob);  // see NOTE 1
    }
        
    const url = blobURL("hello folks and world");
    console.log("2 seconds...");
    setTimeout( saveBlob, 2000, url); // see NOTE 2
    
    function saveBlob(url) {
        const link = document.createElement('a');
        link.setAttribute("download", "hello.txt");
        link.href= url;
        document.body.appendChild(link);
        link.click();
        URL.revokeObjectURL(url); // SEE NOTE 3
        console.log("Call to link.click() has returned");
    }
    

    Note

    1. The script does not keep a reference to the blob created
    2. Memory garbage collection could (in theory) run during a timeout period.
    3. The blob's url is revoked synchronously, after link.click(), before returning to the event loop.

    Calling URL.revokeObjectURL() immediately after programatially clicking the link to download the blob did not affect the success of downloading in Firefox or Edge/webkit when tested. This implies that these browsers synchronously obtains a reference to the Blob instance (using Blob Store lookup) before returning from link.click().

    This is consistent with the behavior of events programmatically dispatched on an element being processed synchronously (which I looked at recently in answer to "Do events bubble in microtasks?"). How safe it is to make use of this in production, however, is not something I am personally in a position to guarantee across all browsers.