generateAsync does not work with Tampermonkey
JSZip doesn't work with Tampermonkey, at least the current newest version, v3.10.1. Version v3.9.1 works just fine.
It is a known problem, see https://github.com/Stuk/jszip/issues/814#issuecomment-1139378561 and https://github.com/cvzi/pinterest-Backup-Original-Files/issues/4 .
I opened this issue, because I've failed to find the conversations above when searching my issue. I only was able to find them when I already knew that this was the problem.
I hope this issue will be easier to find.
Reverting from v3.10.1 to v3.9.1 solved my problem, and it solved their problems too.
It is a regression, and it breaks many existing userscripts in the wild. I suggest, maybe you could revert it?
JSZip doesn't work with Tampermonkey, at least the current newest version, v3.10.1. Version v3.9.1 works just fine.
It is a known problem, see #814 (comment) and cvzi/pinterest-Backup-Original-Files#4 .
I opened this issue, because I've failed to find the conversations above when searching my issue. I only was able to find them when I already knew that this was the problem.
I hope this issue will be easier to find.
Reverting from v3.10.1 to v3.9.1 solved my problem, and it solved their problems too.
There is indeed this issue, but unfortunately it hasn't been updated for a long time.
here is solution -> https://github.com/lisonge/vite-plugin-monkey/issues/216#issuecomment-2745140560
Confirming that I am also experiencing this issue where zip.generateAsync hangs indefinitely when run inside a Tampermonkey userscript.
My environment details:
- Browser: Google Chrome (Latest Stable Version)
- Operating System: Windows 11 24H2
- Tampermonkey Version: 5.3.3
JSZip versions tested:
-
v3.9.1: Works correctly.
generateAsyncresolves and the file is generated/saved. -
v3.10.1: Fails. The script hangs immediately after calling
zip.generateAsync({ type: 'blob' }). The promise never resolves or rejects.
The issue can be reproduced reliably using the following minimal standalone Tampermonkey test script:
// ==UserScript==
// @name JSZip Standalone Functionality Test (for GitHub Issue #934)
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Standalone test for JSZip generateAsync hang in Tampermonkey
// @author Test User
// @match https://example.com/
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js
// @grant GM_addStyle
// ==/UserScript==
/* global JSZip, saveAs */
(function() {
'use strict';
console.log("JSZip Test Script: Initializing...");
// --- Add styles ---
GM_addStyle(`
#jszip-test-button {
position: fixed; bottom: 20px; right: 20px; z-index: 9999;
padding: 10px 15px; background-color: #FF6347; color: white;
border: none; border-radius: 5px; cursor: pointer; font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
#jszip-test-button:disabled {
background-color: #cccccc; cursor: not-allowed;
}
`);
// --- Create test button ---
let testButton = document.createElement('button');
testButton.id = 'jszip-test-button';
testButton.innerText = 'Run JSZip Test';
document.body.appendChild(testButton);
// --- Button click handler ---
testButton.addEventListener('click', function() {
console.log("JSZip Test Button Clicked!");
testButton.disabled = true;
testButton.innerText = 'Testing...';
try {
console.log("1. Creating JSZip instance...");
const zip = new JSZip();
console.log("2. Creating folder 'test_files'...");
const folder = zip.folder("test_files");
console.log("3. Creating test Blob...");
const testContent = `Hello from JSZip Standalone Test!\nTime: ${new Date().toISOString()}\nRandom: ${Math.random()}`;
const testBlob = new Blob([testContent], { type: 'text/plain;charset=utf-8' });
console.log(" Test Blob created, size:", testBlob.size);
console.log("4. Converting Blob to ArrayBuffer...");
testBlob.arrayBuffer().then(buffer => {
console.log(" ArrayBuffer conversion success, adding to ZIP...");
try {
folder.file("hello.txt", buffer); // Add file to folder
console.log("5. File 'test_files/hello.txt' added to JSZip object.");
} catch(addFileError) {
console.error(" Error adding file to zip:", addFileError);
alert("Error adding file to zip! Check console.");
testButton.disabled = false; testButton.innerText = 'Run JSZip Test';
return;
}
console.log("6. Preparing to call zip.generateAsync({ type: 'blob' })...");
// Note: The @require line above uses 3.10.1 by default.
// Change it to 3.9.1 to observe the working behavior.
zip.generateAsync({ type: "blob" }) // Simple invocation
.then(generatedBlob => {
// This block is NOT reached with 3.10.1
console.log("7. SUCCESS: zip.generateAsync .then() reached!");
console.log(" Generated Blob size:", generatedBlob.size);
console.log("8. Calling saveAs...");
try {
saveAs(generatedBlob, "jszip_standalone_test.zip");
console.log("9. saveAs call completed.");
alert("JSZip test SUCCESSFUL! File download triggered.");
} catch (saveAsError) {
console.error(" Error during saveAs:", saveAsError);
alert("saveAs failed! Check console.");
}
})
.catch(err => {
// This block is NOT reached when 3.10.1 hangs
console.error("E1: zip.generateAsync FAILED (.catch):", err);
alert("zip.generateAsync failed! Check console.");
})
.finally(() => {
// This block is NOT reached when 3.10.1 hangs
console.log("10. zip.generateAsync .finally() reached.");
testButton.disabled = false;
testButton.innerText = 'Run JSZip Test';
});
// This is the last log message observed when using JSZip 3.10.1
console.log(" Call to zip.generateAsync has been issued (asynchronous).");
}).catch(bufferErr => {
console.error("E2: Error converting Blob to ArrayBuffer:", bufferErr);
alert("Error creating test data! Check console.");
testButton.disabled = false; testButton.innerText = 'Run JSZip Test';
});
} catch (initialError) {
console.error("E3: Initial script error:", initialError);
alert("Test script error! Check console.");
testButton.disabled = false; testButton.innerText = 'Run JSZip Test';
}
});
console.log("JSZip Test Script: Initialized and ready.");
})();
Behavior observed:
- When the
@requireline in the test script uses JSZip v3.10.1, running the test results in the console logging up toCall to zip.generateAsync has been issued (asynchronous).. The script then hangs indefinitely; the.then(),.catch(), and.finally()blocks attached to thegenerateAsyncpromise are never executed. - When the
@requireline is changed to use JSZip v3.9.1, the script runs correctly:generateAsyncresolves, the.then()block is executed logs its messages, andsaveAssuccessfully triggers the file download.
Cause of error
jszip use setImmediate as polyfill
https://github.com/Stuk/jszip/blob/643714aa770afd8fe1df6cfc7e2bde945bb0ef64/lib/utils.js#L7
setImmediate has code if (event.source === global &&
https://github.com/YuzuJS/setImmediate/blob/f1ccbfdf09cb93aadf77c4aa749ea554503b9234/setImmediate.js#L106
when run it tampermonkey scop without // @grant none
event.source actually is globalThis.unsafeWindow, global/globalThis is a Proxy(Object)
the condition is false, so setImmediate callback is not running
Solutions
for @require url
// @require data:application/javascript,%3BglobalThis.setImmediate%3DsetTimeout%3B
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
for vite
export default defineConfig(() => {
return {
plugins: [
{
name: 'fix-setImmediate',
enforce: 'pre',
apply: 'build',
transform(code, id) {
if (id.endsWith('setImmediate.js')) {
return {
code: code.replace(
`if (event.source === global &&`,
`if ((event.source === global || event.source === global.unsafeWindow) &&`,
),
map: null,
};
}
},
},
],
};
});
I'm also using JSZip 3.10.1 with Tampermonkey. Therefore a dynamically load the JSZip.js
Before I used:
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "https://raw.githubusercontent.com/Stuk/jszip/refs/heads/main/dist/jszip.min.js", false);
xmlHttp.send(null);
eval(xmlHttp.responseText);
console.log("JSZip loaded: " + JSZip.version);
result(new JSZip());
but then .generateAsync doesn't work. No result and no reject from the promise.
But using unsafeWindow for the evaluation does the trick:
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "https://raw.githubusercontent.com/Stuk/jszip/refs/heads/main/dist/jszip.min.js", false);
xmlHttp.send(null);
unsafeWindow.eval(xmlHttp.responseText);
console.log("JSZip loaded: " + JSZip.version);
result(new JSZip());
after that everything works as intended.