Background Network Requests Indicator

Shows an indicator at bottom right/left when there is one or more background network requests in progress.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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

})();