Background Network Requests Indicator

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

目前为 2023-07-27 提交的版本,查看 最新版本

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