您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
github.com/Tampermonkey/tampermonkey/issues/2215
此脚本不应直接安装,它是供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://updategreasyfork.deno.dev/scripts/522123/1511104/tampermonkey%20parallel.js
/* * https://github.com/Tampermonkey/tampermonkey/issues/2215 * * This script provides a workaround for a Chrome MV3 issue (https://github.com/w3c/webextensions/issues/694) * where extensions can't set/delete headers that are preserved over redirects. * * By setting `redirect: 'manual'` and following redirects manually, this script ensures request redirects work * as intended and requests to different URLs are made in parallel (again). * * Userscript authors should include this as a `@require` when they need to make parallel requests with GM_xmlhttpRequest, * especially if requests might take a long time to complete. * * Including this script will modify the behavior of GM_xmlhttpRequest and GM.xmlHttpRequest in Tampermonkey only. * * Usage: * * Add this to the metadata block of your userscript: * * // @grant GM_xmlhttpRequest * // @require https://raw.githubusercontent.com/Tampermonkey/utils/refs/heads/main/requires/gh_2215_make_GM_xhr_more_parallel_again.js * **/ /* global GM_info, GM_xmlhttpRequest, GM */ const HAS_GM = typeof GM !== "undefined"; const NEW_GM = ((scope, GM) => { // Check if running in Tampermonkey and if version supports redirect control if ( GM_info.scriptHandler !== "Tampermonkey" || compareVersions(GM_info.version, "5.3.2") < 0 ) return; // Backup original functions const GM_xmlhttpRequestOrig = GM_xmlhttpRequest; const GM_xmlHttpRequestOrig = GM.xmlHttpRequest; function compareVersions(v1, v2) { const parts1 = v1.split(".").map(Number); const parts2 = v2.split(".").map(Number); const length = Math.max(parts1.length, parts2.length); for (let i = 0; i < length; i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 > num2) return 1; if (num1 < num2) return -1; } return 0; } // Wrapper for GM_xmlhttpRequest function GM_xmlhttpRequestWrapper(odetails) { // If redirect is manually set, simply pass odetails to the original function if (odetails.redirect !== undefined) { return GM_xmlhttpRequestOrig(odetails); } // Warn if onprogress is used with settings incompatible with fetch mode used in background if (odetails.onprogress || odetails.fetch === false) { console.warn("Fetch mode does not support onprogress in the background."); } const { onload, onloadend, onerror, onabort, ontimeout, ...details } = odetails; // Set redirect to manual and handle redirects const handleRedirects = (initialDetails) => { const request = GM_xmlhttpRequestOrig({ ...initialDetails, redirect: "manual", onload: function (response) { if (response.status >= 300 && response.status < 400) { const m = response.responseHeaders.match(/Location:\s*(\S+)/i); // Follow redirect manually const redirectUrl = m && m[1]; if (redirectUrl) { const absoluteUrl = new URL(redirectUrl, initialDetails.url).href; handleRedirects({ ...initialDetails, url: absoluteUrl }); return; } } if (onload) onload.call(this, response); if (onloadend) onloadend.call(this, response); }, onerror: function (response) { if (onerror) onerror.call(this, response); if (onloadend) onloadend.call(this, response); }, onabort: function (response) { if (onabort) onabort.call(this, response); if (onloadend) onloadend.call(this, response); }, ontimeout: function (response) { if (ontimeout) ontimeout.call(this, response); if (onloadend) onloadend.call(this, response); }, }); return request; }; return handleRedirects(details); } // Wrapper for GM.xmlHttpRequest function GM_xmlHttpRequestWrapper(odetails) { let abort; const p = new Promise((resolve, reject) => { const { onload, ontimeout, onerror, ...send } = odetails; send.onerror = function (r) { if (onerror) { resolve(r); onerror.call(this, r); } else { reject(r); } }; send.ontimeout = function (r) { if (ontimeout) { // See comment above resolve(r); ontimeout.call(this, r); } else { reject(r); } }; send.onload = function (r) { resolve(r); if (onload) onload.call(this, r); }; const a = GM_xmlhttpRequestWrapper(send).abort; if (abort === true) { a(); } else { abort = a; } }); p.abort = () => { if (typeof abort === "function") { abort(); } else { abort = true; } }; return p; } // Export wrappers GM_xmlhttpRequest = GM_xmlhttpRequestWrapper; scope.GM_xmlhttpRequestOrig = GM_xmlhttpRequestOrig; const gopd = Object.getOwnPropertyDescriptor(GM, "xmlHttpRequest"); if (gopd && gopd.configurable === false) { return { __proto__: GM, xmlHttpRequest: GM_xmlHttpRequestWrapper, xmlHttpRequestOrig: GM_xmlHttpRequestOrig, }; } else { GM.xmlHttpRequest = GM_xmlHttpRequestWrapper; GM.xmlHttpRequestOrig = GM_xmlHttpRequestOrig; } })(this, HAS_GM ? GM : {}); if (HAS_GM && NEW_GM) GM = NEW_GM;