Greasy Fork is available in English.

Background Network Requests Indicator

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

  1. // ==UserScript==
  2. // @name Background Network Requests Indicator
  3. // @namespace BackgroundNetworkRequestsIndicator
  4. // @version 1.1.21
  5. // @license AGPL v3
  6. // @author jcunews
  7. // @description Shows an indicator at bottom right/left when there is one or more background network requests in progress.
  8. // @website https://greasyfork.org/en/users/85671-jcunews
  9. // @match *://*/*
  10. // @inject-into page
  11. // @grant none
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. /*
  16. The number on the indicator shows the number of background network requests in progress.
  17.  
  18. When it shows, by default it will be placed at bottom-right. When the mouse cursor is
  19. moved to the right half area of the page, the indicator will move itself to the bottom-left.
  20.  
  21. If the SHIFT key is held down, the indicator will stay. And when the mouse cursor is on it,
  22. a list of pending network request URLs will be shown.
  23. */
  24.  
  25. ((eleContainer, eleStyle, eleList, eleIndicator, xhrId, xhrCount, xhrAbort, xhrOpen, xhrSend, shiftPressed) => {
  26.  
  27. if (!(document instanceof HTMLDocument)) return;
  28.  
  29. var to = {createHTML: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to, html = s => tp.createHTML(s);
  30.  
  31. (eleContainer = document.createElement("DIV")).id = "bnriContainer";
  32. eleContainer.innerHTML = html(`<style>
  33. #bnriContainer, #bnriList, #bnriList>.url, #bnriIndicator {
  34. display:block!important; opacity:1!important; visibility:visible!important;
  35. position:static!important; float:none!important; margin:0!important;
  36. box-sizing:content-box!important; border:none!important; padding:0!important;
  37. width:auto!important; min-width:0!important; max-width:none!important;
  38. height:auto!important; min-height:0!important; max-height:none!important;
  39. background:transparent!important; font:10pt/normal sans-serif!important;
  40. }
  41. #bnriContainer {
  42. position:fixed!important; z-index:9999999999!important; left:auto!important;
  43. top:auto!important; right:0!important; bottom:.5em!important;
  44. }
  45. #bnriContainer.left, #bnriContainer.left #bnriList {
  46. left:0!important; right:auto!important;
  47. }
  48. #bnriList {
  49. display:none!important; position:fixed!important; left:auto!important; top:auto!important;
  50. right:0!important; bottom:1.7em!important; border:1px solid #555!important;
  51. max-height:50vw!important; overflow-x:hidden!important; overflow-y:auto!important; background-color:#ddd!important;
  52. }
  53. #bnriContainer:hover>#bnriList {
  54. display:block!important;
  55. }
  56. #bnriList>.url {
  57. max-width:90vw!important; padding:0 .2em!important; line-height:1.5em!important;
  58. white-space: nowrap!important; text-overflow:ellipsis!important; color: #000!important;
  59. }
  60. #bnriList>.url:nth-child(2n) {
  61. background-color:#ccc!important;
  62. }
  63. #bnriIndicator {
  64. border:1mm solid #bb0!important; border-radius:2em!important;
  65. padding:0 1mm!important; background-color:#ff0!important; text-align:center!important;
  66. color:#000!important; cursor:default!important;
  67. }
  68. </style>
  69. <div id="bnriList"></div>
  70. <div id="bnriIndicator"></div>
  71. `);
  72. eleList = eleContainer.querySelector("#bnriList");
  73. eleIndicator = eleContainer.querySelector("#bnriIndicator");
  74.  
  75. xhrId = xhrCount = 0;
  76.  
  77. function checkCursor(ev) {
  78. if (!shiftPressed) {
  79. if (ev.clientX >= Math.floor(innerWidth / 2)) {
  80. eleContainer.className = "left";
  81. } else eleContainer.className = "";
  82. }
  83. }
  84.  
  85. function doneRequest(xhr) {
  86. if (xhr.id_bnri && (--xhrCount < 0)) xhrCount = 0;
  87. delete xhr.id_bnri;
  88. if (xhr.ele_bnri && xhr.ele_bnri.parentNode) {
  89. xhr.ele_bnri.parentNode.removeChild(xhr.ele_bnri); //ignorant Metodize library broke Element.prototype.remove()
  90. delete xhr.ele_bnri;
  91. }
  92. if (xhrCount) {
  93. eleIndicator.textContent = xhrCount;
  94. } else if (eleContainer.parentNode) {
  95. removeEventListener("mousemove", checkCursor);
  96. document.body.removeChild(eleContainer);
  97. setTimeout(() => { //workaround when element isn't removed somehow
  98. if (!xhrCount && eleContainer.parentNode) document.body.removeChild(eleContainer);
  99. }, 0);
  100. }
  101. }
  102.  
  103. function doneEvent(ev) {
  104. doneRequest(ev.target)
  105. }
  106.  
  107. function checkState() {
  108. if ((this.readyState >= XMLHttpRequest.HEADERS_RECEIVED) && !eleContainer.parentNode && document.body) {
  109. document.body.appendChild(eleContainer);
  110. addEventListener("mousemove", checkCursor);
  111. }
  112. if ((this.readyState !== XMLHttpRequest.DONE) || !this.id_bnri) return;
  113. doneRequest(this);
  114. }
  115.  
  116. xhrAbort = XMLHttpRequest.prototype.abort;
  117. XMLHttpRequest.prototype.abort = function() {
  118. doneRequest(this);
  119. return xhrAbort.apply(this, arguments);
  120. };
  121.  
  122. xhrOpen = XMLHttpRequest.prototype.open;
  123. XMLHttpRequest.prototype.open = function() {
  124. if (!this.url_bnri) {
  125. this.addEventListener("abort", doneEvent);
  126. this.addEventListener("error", doneEvent);
  127. this.addEventListener("load", doneEvent);
  128. this.addEventListener("timeout", doneEvent);
  129. this.addEventListener("readystatechange", checkState);
  130. }
  131. this.url_bnri = arguments[1];
  132. return xhrOpen.apply(this, arguments);
  133. };
  134.  
  135. xhrSend = XMLHttpRequest.prototype.send;
  136. XMLHttpRequest.prototype.send = function() {
  137. if (!this.id_bnri) {
  138. this.id_bnri = ++xhrId;
  139. (this.ele_bnri = eleList.appendChild(document.createElement("DIV"))).className = "url";
  140. this.ele_bnri.textContent = "XHR: " + this.url_bnri;
  141. }
  142. eleIndicator.textContent = ++xhrCount;
  143. if (!eleContainer.parentNode && document.body) {
  144. document.body.appendChild(eleContainer);
  145. addEventListener("mousemove", checkCursor);
  146. }
  147. return xhrSend.apply(this, arguments);
  148. };
  149.  
  150. var ffetch = window.fetch;
  151. window.fetch = function(urlReq, opts) {
  152. var context = {urlReq: opts || urlReq, id_bnri: ++xhrId, ele_bnri: eleList.appendChild(document.createElement("DIV"))};
  153. context.ele_bnri.className = "url";
  154. context.ele_bnri.textContent = "fetch: " + (urlReq.url || urlReq);
  155. eleIndicator.textContent = ++xhrCount;
  156. if (!eleContainer.parentNode && document.body) {
  157. document.body.appendChild(eleContainer);
  158. addEventListener("mousemove", checkCursor);
  159. }
  160. function doneFetch() {
  161. doneRequest(context);
  162. }
  163. var a = "finally_bnri" + (urlReq.url || urlReq);
  164. window.fetch[a] = doneFetch;
  165. var res = ffetch.apply(this, arguments).finally(doneFetch);
  166. setTimeout(a => {
  167. delete window.fetch[a]
  168. }, window.fetch.timeout_bnri || 10000, a);
  169. return res;
  170. };
  171.  
  172. var nac = Node.prototype.appendChild;
  173. Node.prototype.appendChild = function(e) {
  174. var z;
  175. if ((this.tagName === "BODY") && (e?.tagName === "IFRAME")) {
  176. var r = nac.apply(this, arguments);
  177. try {
  178. if (/^about:blank\b/.test(e.contentWindow.location.href)) e.contentWindow.fetch = fetch
  179. } catch(z) {}
  180. return r
  181. } else return nac.apply(this, arguments)
  182. }
  183.  
  184. addEventListener("keydown", e => {
  185. if (e.key === "Shift") shiftPressed = true;
  186. });
  187.  
  188. addEventListener("keyup", e => {
  189. if (e.key === "Shift") shiftPressed = false;
  190. });
  191.  
  192. })();