Shows an indicator at bottom right/left when there is one or more background network requests in progress.
// ==UserScript== // @name Background Network Requests Indicator // @namespace BackgroundNetworkRequestsIndicator // @version 1.1.21 // @license AGPL v3 // @author jcunews // @description Shows an indicator at bottom right/left when there is one or more background network requests in progress. // @website https://greasyfork.org/en/users/85671-jcunews // @match *://*/* // @inject-into page // @grant none // @run-at document-start // ==/UserScript== /* The number on the indicator shows the number of background network requests in progress. When it shows, by default it will be placed at bottom-right. When the mouse cursor is moved to the right half area of the page, the indicator will move itself to the bottom-left. If the SHIFT key is held down, the indicator will stay. And when the mouse cursor is on it, a list of pending network request URLs will be shown. */ ((eleContainer, eleStyle, eleList, eleIndicator, xhrId, xhrCount, xhrAbort, xhrOpen, xhrSend, shiftPressed) => { if (!(document instanceof HTMLDocument)) return; var to = {createHTML: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to, html = s => tp.createHTML(s); (eleContainer = document.createElement("DIV")).id = "bnriContainer"; eleContainer.innerHTML = html(`<style> #bnriContainer, #bnriList, #bnriList>.url, #bnriIndicator { display:block!important; opacity:1!important; visibility:visible!important; position:static!important; float:none!important; margin:0!important; box-sizing:content-box!important; border:none!important; padding:0!important; width:auto!important; min-width:0!important; max-width:none!important; height:auto!important; min-height:0!important; max-height:none!important; background:transparent!important; font:10pt/normal sans-serif!important; } #bnriContainer { position:fixed!important; z-index:9999999999!important; left:auto!important; top:auto!important; right:0!important; bottom:.5em!important; } #bnriContainer.left, #bnriContainer.left #bnriList { left:0!important; right:auto!important; } #bnriList { display:none!important; position:fixed!important; left:auto!important; top:auto!important; right:0!important; bottom:1.7em!important; border:1px solid #555!important; max-height:50vw!important; overflow-x:hidden!important; overflow-y:auto!important; background-color:#ddd!important; } #bnriContainer:hover>#bnriList { display:block!important; } #bnriList>.url { max-width:90vw!important; padding:0 .2em!important; line-height:1.5em!important; white-space: nowrap!important; text-overflow:ellipsis!important; color: #000!important; } #bnriList>.url:nth-child(2n) { background-color:#ccc!important; } #bnriIndicator { border:1mm solid #bb0!important; border-radius:2em!important; padding:0 1mm!important; background-color:#ff0!important; text-align:center!important; color:#000!important; cursor:default!important; } </style> <div id="bnriList"></div> <div id="bnriIndicator"></div> `); eleList = eleContainer.querySelector("#bnriList"); eleIndicator = eleContainer.querySelector("#bnriIndicator"); xhrId = xhrCount = 0; function checkCursor(ev) { if (!shiftPressed) { if (ev.clientX >= Math.floor(innerWidth / 2)) { eleContainer.className = "left"; } else eleContainer.className = ""; } } function doneRequest(xhr) { if (xhr.id_bnri && (--xhrCount < 0)) xhrCount = 0; delete xhr.id_bnri; if (xhr.ele_bnri && xhr.ele_bnri.parentNode) { xhr.ele_bnri.parentNode.removeChild(xhr.ele_bnri); //ignorant Metodize library broke Element.prototype.remove() delete xhr.ele_bnri; } if (xhrCount) { eleIndicator.textContent = xhrCount; } else if (eleContainer.parentNode) { removeEventListener("mousemove", checkCursor); document.body.removeChild(eleContainer); setTimeout(() => { //workaround when element isn't removed somehow if (!xhrCount && eleContainer.parentNode) document.body.removeChild(eleContainer); }, 0); } } function doneEvent(ev) { doneRequest(ev.target) } function checkState() { if ((this.readyState >= XMLHttpRequest.HEADERS_RECEIVED) && !eleContainer.parentNode && document.body) { document.body.appendChild(eleContainer); addEventListener("mousemove", checkCursor); } if ((this.readyState !== XMLHttpRequest.DONE) || !this.id_bnri) return; doneRequest(this); } xhrAbort = XMLHttpRequest.prototype.abort; XMLHttpRequest.prototype.abort = function() { doneRequest(this); return xhrAbort.apply(this, arguments); }; xhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function() { if (!this.url_bnri) { this.addEventListener("abort", doneEvent); this.addEventListener("error", doneEvent); this.addEventListener("load", doneEvent); this.addEventListener("timeout", doneEvent); this.addEventListener("readystatechange", checkState); } this.url_bnri = arguments[1]; return xhrOpen.apply(this, arguments); }; xhrSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { if (!this.id_bnri) { this.id_bnri = ++xhrId; (this.ele_bnri = eleList.appendChild(document.createElement("DIV"))).className = "url"; this.ele_bnri.textContent = "XHR: " + this.url_bnri; } eleIndicator.textContent = ++xhrCount; if (!eleContainer.parentNode && document.body) { document.body.appendChild(eleContainer); addEventListener("mousemove", checkCursor); } return xhrSend.apply(this, arguments); }; var ffetch = window.fetch; window.fetch = function(urlReq, opts) { var context = {urlReq: opts || urlReq, id_bnri: ++xhrId, ele_bnri: eleList.appendChild(document.createElement("DIV"))}; context.ele_bnri.className = "url"; context.ele_bnri.textContent = "fetch: " + (urlReq.url || urlReq); eleIndicator.textContent = ++xhrCount; if (!eleContainer.parentNode && document.body) { document.body.appendChild(eleContainer); addEventListener("mousemove", checkCursor); } function doneFetch() { doneRequest(context); } var a = "finally_bnri" + (urlReq.url || urlReq); window.fetch[a] = doneFetch; var res = ffetch.apply(this, arguments).finally(doneFetch); setTimeout(a => { delete window.fetch[a] }, window.fetch.timeout_bnri || 10000, a); return res; }; var nac = Node.prototype.appendChild; Node.prototype.appendChild = function(e) { var z; if ((this.tagName === "BODY") && (e?.tagName === "IFRAME")) { var r = nac.apply(this, arguments); try { if (/^about:blank\b/.test(e.contentWindow.location.href)) e.contentWindow.fetch = fetch } catch(z) {} return r } else return nac.apply(this, arguments) } addEventListener("keydown", e => { if (e.key === "Shift") shiftPressed = true; }); addEventListener("keyup", e => { if (e.key === "Shift") shiftPressed = false; }); })();