// ==UserScript== // @name Abort Network Requests on Escape // @namespace blankie-scripts // @match http*://*/* // @grant none // @version 1.0.0 // @author blankie // @description Aborts network requests invoked from XMLHttpRequest and fetch() when Escape is pressed // @run-at document-start // @inject-into page // ==/UserScript== "use strict"; let activeXHRs = new Set(); let abortControllers = new Set(); unsafeWindow.XMLHttpRequest = new Proxy(unsafeWindow.XMLHttpRequest, { construct: function(target, args) { let xhr = new target(...args); xhr.addEventListener("loadstart", function() { activeXHRs.add(this); }, {passive: true}); xhr.addEventListener("loadend", function() { activeXHRs.delete(this); }, {passive: true}); return xhr; } }); let originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = function(resource, options = undefined) { let abortController = new AbortController(); options = options || {}; let originalSignal = options.signal; if (originalSignal) { originalSignal.addEventListener("abort", function() { abortController.abort(originalSignal.reason); }, {passive: true}); } options.signal = abortController.signal; abortControllers.add(abortController); return originalFetch.call(this, resource, options).finally(function() { abortControllers.delete(abortController); }); } function abortAllRequests() { for (let activeXHR of activeXHRs) { activeXHR.abort(); } activeXHRs.clear(); let error = new DOMException("Network request cancelled by Abort Network Requests on Escape", "NetworkError"); for (let abortController of abortControllers) { abortController.abort(error); } abortControllers.clear(); } window.addEventListener("keydown", function(event) { if (event.key !== "Escape" || event.target !== document.body || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { return; } abortAllRequests(); }, {passive: true});