// ==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;
});
})();