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
- The script does not keep a reference to the blob created
- Memory garbage collection could (in theory) run during a timeout period.
- 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.