Background Network Requests Indicator

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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;
  });

})();