Background Network Requests Indicator

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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

})();